├── sui ├── .gitignore ├── Move.toml ├── Move.testnet.toml ├── tsconfig.json ├── package.json ├── Move.lock ├── CHANGELOG.md └── DEPLOYMENT.md ├── evm ├── .gitignore ├── price-feeds │ ├── remappings.txt │ ├── foundry.toml │ ├── tsconfig.json │ ├── package.json │ ├── deploy │ │ └── DeploySwitchboardPriceConsumer.s.sol │ ├── src │ │ └── switchboard │ │ │ ├── libraries │ │ │ └── SwitchboardTypes.sol │ │ │ └── interfaces │ │ │ └── ISwitchboard.sol │ └── scripts │ │ └── utils.ts ├── randomness │ ├── coin-flip │ │ ├── remappings.txt │ │ ├── foundry.toml │ │ ├── .gitignore │ │ ├── package.json │ │ ├── deploy │ │ │ └── CoinFlip.s.sol │ │ ├── .github │ │ │ └── workflows │ │ │ │ └── test.yml │ │ ├── scripts │ │ │ └── flip-coin.ts │ │ └── src │ │ │ └── CoinFlip.sol │ └── pancake-stacker │ │ ├── remappings.txt │ │ ├── foundry.toml │ │ ├── .gitignore │ │ ├── package.json │ │ ├── server.ts │ │ ├── deploy │ │ └── PancakeStacker.s.sol │ │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ │ ├── scripts │ │ └── stack-pancake.ts │ │ ├── src │ │ └── PancakeStacker.sol │ │ └── README.md └── legacy │ ├── script │ └── Deploy.s.sol │ ├── examples │ ├── utils.ts │ └── updateFeed.ts │ ├── src │ └── Example.sol │ └── README.md ├── solana ├── prediction-market │ ├── programs │ │ └── prediction-market │ │ │ ├── Xargo.toml │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ └── lib.rs │ ├── Anchor.toml │ ├── Cargo.toml │ └── package.json ├── randomness │ ├── coin-flip │ │ ├── programs │ │ │ └── sb-randomness │ │ │ │ ├── Xargo.toml │ │ │ │ └── Cargo.toml │ │ ├── scripts │ │ │ ├── tsconfig.json │ │ │ └── README.md │ │ ├── Anchor.toml │ │ ├── Cargo.toml │ │ └── package.json │ └── pancake-stacker │ │ ├── Anchor.toml │ │ ├── Cargo.toml │ │ ├── programs │ │ └── pancake-stacker │ │ │ └── Cargo.toml │ │ ├── server.ts │ │ ├── package.json │ │ └── scripts │ │ └── utils.ts ├── legacy │ ├── variable-overrides │ │ ├── programs │ │ │ └── sb-on-demand-solana │ │ │ │ ├── Xargo.toml │ │ │ │ ├── Cargo.toml │ │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── Anchor.toml │ │ ├── tsconfig.json │ │ ├── Cargo.toml │ │ ├── package.json │ │ ├── scripts │ │ │ ├── test-simple.ts │ │ │ └── utils │ │ │ │ └── utils.ts │ │ └── README.md │ ├── feeds │ │ ├── README.md │ │ ├── runFeed.ts │ │ └── view.ts │ └── benchmarks │ │ └── benchmarkCU.ts ├── feeds │ ├── basic │ │ ├── Anchor.toml │ │ ├── Cargo.toml │ │ ├── programs │ │ │ └── basic-oracle-example │ │ │ │ ├── Cargo.toml │ │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── package.json │ │ └── scripts │ │ │ ├── managedUpdate.ts │ │ │ ├── README.md │ │ │ └── createManagedFeed.ts │ └── advanced │ │ ├── Cargo.toml │ │ ├── Anchor.toml │ │ ├── programs │ │ └── advanced-oracle-example │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ ├── lib.rs │ │ │ └── utils.rs │ │ ├── package.json │ │ └── scripts │ │ └── README.md ├── surge │ ├── package.json │ └── README.md └── x402 │ └── package.json ├── .gitignore ├── .gitmodules ├── common ├── twitter-follower-count │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ ├── oauth.ts │ └── getFollowerCount.ts ├── rust-feed-creation │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── package.json ├── variable-overrides │ ├── package.json │ └── testVariableOverrides.ts ├── streaming │ ├── crossbarStream.ts │ └── README.md ├── job-testing │ └── runJob.ts └── README.md ├── AGENTS.md └── README.md /sui/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /evm/.gitignore: -------------------------------------------------------------------------------- 1 | broadcast/ 2 | cache/ 3 | out/ -------------------------------------------------------------------------------- /evm/price-feeds/remappings.txt: -------------------------------------------------------------------------------- 1 | @switchboard-xyz/on-demand-solidity/=node_modules/@switchboard-xyz/on-demand-solidity/ -------------------------------------------------------------------------------- /solana/prediction-market/programs/prediction-market/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /solana/randomness/coin-flip/programs/sb-randomness/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /evm/randomness/coin-flip/remappings.txt: -------------------------------------------------------------------------------- 1 | @switchboard-xyz/on-demand-solidity/=node_modules/@switchboard-xyz/on-demand-solidity 2 | 3 | -------------------------------------------------------------------------------- /evm/randomness/pancake-stacker/remappings.txt: -------------------------------------------------------------------------------- 1 | @switchboard-xyz/on-demand-solidity/=node_modules/@switchboard-xyz/on-demand-solidity 2 | 3 | -------------------------------------------------------------------------------- /solana/legacy/variable-overrides/programs/sb-on-demand-solana/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | .yarn 9 | **/target 10 | **/.env 11 | .env 12 | broadcast/ 13 | cache/ 14 | out/ 15 | **/**/bun.lockb 16 | -------------------------------------------------------------------------------- /evm/price-feeds/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | script = "deploy" 6 | 7 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 8 | -------------------------------------------------------------------------------- /evm/randomness/coin-flip/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | script = "deploy" 6 | 7 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 8 | -------------------------------------------------------------------------------- /evm/randomness/pancake-stacker/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | script = "deploy" 6 | 7 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "evm/lib/forge-std"] 2 | path = evm/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "evm/randomness/lib/forge-std"] 5 | path = evm/randomness/lib/forge-std 6 | url = https://github.com/foundry-rs/forge-std 7 | -------------------------------------------------------------------------------- /solana/legacy/variable-overrides/Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | seeds = false 5 | skip-lint = false 6 | 7 | [registry] 8 | url = "https://api.apr.dev" 9 | 10 | [provider] 11 | cluster = "Devnet" 12 | wallet = "~/.config/solana/id.json" 13 | -------------------------------------------------------------------------------- /solana/legacy/variable-overrides/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /sui/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | edition = "2024.beta" 4 | version = "0.0.1" 5 | 6 | [dependencies.Switchboard] 7 | git = "https://github.com/switchboard-xyz/sui.git" 8 | subdir = "on_demand" 9 | rev = "testnet" 10 | 11 | [addresses] 12 | example = "0x0" 13 | std = "0x1" 14 | sui = "0x2" 15 | 16 | -------------------------------------------------------------------------------- /evm/randomness/coin-flip/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Foundry libraries (installed via forge install) 6 | lib/ 7 | 8 | # Ignores development broadcast logs 9 | !/broadcast 10 | /broadcast/*/31337/ 11 | /broadcast/**/dry-run/ 12 | 13 | # Docs 14 | docs/ 15 | 16 | # Dotenv file 17 | .env 18 | -------------------------------------------------------------------------------- /solana/legacy/variable-overrides/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "programs/*" 5 | ] 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 15 | -------------------------------------------------------------------------------- /evm/randomness/pancake-stacker/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Foundry libraries (installed via forge install) 6 | lib/ 7 | 8 | # Ignores development broadcast logs 9 | !/broadcast 10 | /broadcast/*/31337/ 11 | /broadcast/**/dry-run/ 12 | 13 | # Docs 14 | docs/ 15 | 16 | # Dotenv file 17 | .env 18 | -------------------------------------------------------------------------------- /solana/randomness/coin-flip/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "commonjs", 5 | "allowSyntheticDefaultImports": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "skipLibCheck": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /common/twitter-follower-count/.gitignore: -------------------------------------------------------------------------------- 1 | # Environment variables 2 | .env 3 | .env.local 4 | 5 | # Node modules 6 | node_modules/ 7 | 8 | # Build output 9 | dist/ 10 | *.js 11 | *.d.ts 12 | *.js.map 13 | 14 | # TypeScript cache 15 | *.tsbuildinfo 16 | 17 | # IDE 18 | .vscode/ 19 | .idea/ 20 | 21 | # OS 22 | .DS_Store 23 | Thumbs.db 24 | -------------------------------------------------------------------------------- /sui/Move.testnet.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | edition = "2024.beta" 4 | version = "0.0.1" 5 | 6 | [dependencies.Switchboard] 7 | git = "https://github.com/switchboard-xyz/sui.git" 8 | subdir = "on_demand" 9 | rev = "testnet" 10 | 11 | [addresses] 12 | example = "0x0" 13 | std = "0x1" 14 | sui = "0x2" 15 | switchboard = "0x28005599a66e977bff26aeb1905a02cda5272fd45bb16a5a9eb38e8659658cff" 16 | 17 | -------------------------------------------------------------------------------- /common/rust-feed-creation/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-feed-creation" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | switchboard-on-demand = { version = "0.11.3", features = ["client"] } 8 | tokio = { version = "1.0", features = ["full"] } 9 | serde_json = "1.0" 10 | anyhow = "1.0" 11 | solana-sdk = "2.0" 12 | reqwest = { version = "0.11", features = ["json"] } 13 | rust_decimal = "1.0" 14 | -------------------------------------------------------------------------------- /solana/randomness/pancake-stacker/Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | resolution = true 5 | skip-lint = true 6 | 7 | [programs.devnet] 8 | pancake_stacker = "PANCAKE_STACKER_PROGRAM_ID" 9 | 10 | [programs.mainnet] 11 | pancake_stacker = "PANCAKE_STACKER_PROGRAM_ID" 12 | 13 | [registry] 14 | url = "https://api.apr.dev" 15 | 16 | [provider] 17 | cluster = "devnet" 18 | wallet = "~/.config/solana/id.json" 19 | -------------------------------------------------------------------------------- /solana/randomness/coin-flip/Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | resolution = true 5 | skip-lint = true 6 | 7 | [programs.devnet] 8 | sb_randomness = "44wNsyMPUQxCDhJpKqwNu8XGjqYbThqgRPUpQCARyrUc" 9 | 10 | [programs.mainnet] 11 | sb_randomness = "93tkpep2PYDxweHHi2vQBpi7eTBF23y8LGdiLMt5R9f2" 12 | 13 | [registry] 14 | url = "https://api.apr.dev" 15 | 16 | [provider] 17 | cluster = "devnet" 18 | wallet = "~/.config/solana/id.json" 19 | -------------------------------------------------------------------------------- /solana/feeds/basic/Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | resolution = true 5 | skip-lint = true 6 | 7 | [programs.devnet] 8 | basic_oracle_example = "4ZK7w1R7R4GJrFBSUZrNPnmzsUeG8bvVKvk6z7bvQ2zZ" 9 | 10 | [programs.mainnet] 11 | basic_oracle_example = "9kVBXoCrvZgKYWTJ74w3S8wAp7daEB7zpG7kwiXxkCVN" 12 | 13 | [registry] 14 | url = "https://api.apr.dev" 15 | 16 | [provider] 17 | cluster = "devnet" 18 | wallet = "~/.config/solana/id.json" 19 | -------------------------------------------------------------------------------- /solana/feeds/basic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["programs/basic-oracle-example"] 4 | 5 | [profile.release] 6 | opt-level = 3 7 | lto = true 8 | codegen-units = 1 9 | panic = "abort" 10 | overflow-checks = true 11 | strip = "symbols" 12 | 13 | [profile.release.build-override] 14 | opt-level = 3 15 | incremental = false 16 | codegen-units = 1 17 | 18 | [profile.bpf] 19 | inherits = "release" 20 | overflow-checks = false 21 | -------------------------------------------------------------------------------- /solana/prediction-market/Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | resolution = true 5 | skip-lint = true 6 | 7 | [programs.devnet] 8 | prediction_market = "Cqmj4eALsUv5DJMVKJ2o1FPYBjvp7oCyMrWbvVA38eaK" 9 | 10 | [programs.mainnet] 11 | prediction_market = "Cqmj4eALsUv5DJMVKJ2o1FPYBjvp7oCyMrWbvVA38eaK" 12 | 13 | [registry] 14 | url = "https://api.apr.dev" 15 | 16 | [provider] 17 | cluster = "devnet" 18 | wallet = "~/.config/solana/id.json" 19 | -------------------------------------------------------------------------------- /solana/randomness/coin-flip/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["programs/sb-randomness"] 4 | 5 | [profile.release] 6 | opt-level = 3 7 | lto = true 8 | codegen-units = 1 9 | panic = "abort" 10 | overflow-checks = true 11 | strip = "symbols" 12 | 13 | [profile.release.build-override] 14 | opt-level = 3 15 | incremental = false 16 | codegen-units = 1 17 | 18 | [profile.bpf] 19 | inherits = "release" 20 | overflow-checks = false 21 | -------------------------------------------------------------------------------- /solana/feeds/advanced/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["programs/advanced-oracle-example"] 4 | 5 | [profile.release] 6 | opt-level = 3 7 | lto = true 8 | codegen-units = 1 9 | panic = "abort" 10 | overflow-checks = true 11 | strip = "symbols" 12 | 13 | [profile.release.build-override] 14 | opt-level = 3 15 | incremental = false 16 | codegen-units = 1 17 | 18 | [profile.bpf] 19 | inherits = "release" 20 | overflow-checks = false 21 | -------------------------------------------------------------------------------- /solana/prediction-market/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["programs/prediction-market"] 4 | 5 | [profile.release] 6 | opt-level = 3 7 | lto = true 8 | codegen-units = 1 9 | panic = "abort" 10 | overflow-checks = true 11 | strip = "symbols" 12 | 13 | [profile.release.build-override] 14 | opt-level = 3 15 | incremental = false 16 | codegen-units = 1 17 | 18 | [profile.bpf] 19 | inherits = "release" 20 | overflow-checks = false 21 | -------------------------------------------------------------------------------- /solana/feeds/advanced/Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | resolution = true 5 | skip-lint = true 6 | 7 | [programs.devnet] 8 | advanced_oracle_example = "DKpZoRdw6CkbQPCLuyeQsgLCh3kSaYKhGZ6fvgJCTMgr" 9 | 10 | [programs.mainnet] 11 | advanced_oracle_example = "AtzoHFHwuJs3BiP8zMnaoEo7q3Q7N6ACCUYL7jXhZ5en" 12 | 13 | [registry] 14 | url = "https://api.apr.dev" 15 | 16 | [provider] 17 | cluster = "devnet" 18 | wallet = "~/.config/solana/id.json" 19 | -------------------------------------------------------------------------------- /solana/randomness/pancake-stacker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["programs/pancake-stacker"] 4 | 5 | [profile.release] 6 | opt-level = 3 7 | lto = true 8 | codegen-units = 1 9 | panic = "abort" 10 | overflow-checks = true 11 | strip = "symbols" 12 | 13 | [profile.release.build-override] 14 | opt-level = 3 15 | incremental = false 16 | codegen-units = 1 17 | 18 | [profile.bpf] 19 | inherits = "release" 20 | overflow-checks = false 21 | -------------------------------------------------------------------------------- /common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@switchboard-xyz/common-examples", 3 | "version": "1.0.0", 4 | "description": "Chain-agnostic Switchboard examples", 5 | "type": "module", 6 | "scripts": { 7 | "job": "bun run job-testing/runJob.ts", 8 | "stream": "bun run streaming/crossbarStream.ts", 9 | "twitter": "bun run twitter-follower-count/getFollowerCount.ts" 10 | }, 11 | "dependencies": { 12 | "@switchboard-xyz/common": "^5.4.0", 13 | "yaml": "^2.8.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "lib": ["ES2022"], 6 | "moduleResolution": "bundler", 7 | "resolveJsonModule": true, 8 | "allowJs": true, 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "types": ["node"] 14 | }, 15 | "include": ["scripts/**/*", "examples/**/*"], 16 | "exclude": ["node_modules", "build"] 17 | } 18 | 19 | -------------------------------------------------------------------------------- /evm/randomness/coin-flip/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coin-flip", 3 | "version": "1.0.0", 4 | "description": "Coin flip example with wagering using Switchboard randomness", 5 | "type": "module", 6 | "scripts": { 7 | "flip": "bun run scripts/flip-coin.ts", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Switchboard Labs", 11 | "license": "MIT", 12 | "dependencies": { 13 | "@switchboard-xyz/common": "^5.5.1", 14 | "@switchboard-xyz/on-demand-solidity": "^1.1.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /solana/legacy/variable-overrides/programs/sb-on-demand-solana/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sb-on-demand-secrets" 3 | version = "0.1.0" 4 | description = "Switchboard On-Demand Secrets Integration" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "sb_on_demand_secrets" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | idl-build = ["anchor-lang/idl-build"] 18 | 19 | [dependencies] 20 | anchor-lang = "0.31.1" 21 | switchboard-on-demand = "0.4.7" 22 | -------------------------------------------------------------------------------- /evm/randomness/pancake-stacker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pancake-stacker", 3 | "version": "1.0.0", 4 | "description": "Pancake stacking game using Switchboard randomness", 5 | "type": "module", 6 | "scripts": { 7 | "start": "bun run server.ts", 8 | "flip": "bun run scripts/stack-pancake.ts", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "Switchboard Labs", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@switchboard-xyz/common": "^5.5.1", 15 | "@switchboard-xyz/on-demand-solidity": "^1.1.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/twitter-follower-count/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "lib": ["ES2020"], 6 | "outDir": "./dist", 7 | "rootDir": "./", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true, 13 | "moduleResolution": "node", 14 | "declaration": true, 15 | "declarationMap": true, 16 | "sourceMap": true 17 | }, 18 | "include": ["*.ts"], 19 | "exclude": ["node_modules", "dist"] 20 | } 21 | -------------------------------------------------------------------------------- /solana/feeds/advanced/programs/advanced-oracle-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "advanced-oracle-example" 3 | version = "0.1.0" 4 | description = "Advanced Switchboard On-Demand Oracle Integration Example" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "advanced_oracle_example" 10 | 11 | [features] 12 | default = [] 13 | custom-heap = [] 14 | custom-panic = [] 15 | anchor-debug = [] 16 | 17 | [dependencies] 18 | switchboard-on-demand = { version = "0.10.0", features = ["pinocchio", "devnet"] } 19 | bytemuck = "1.14" 20 | pinocchio = { version = "0.9.2", features = ["std"] } 21 | bs58 = "0.5" 22 | 23 | -------------------------------------------------------------------------------- /solana/randomness/coin-flip/programs/sb-randomness/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | resolver = "2" 3 | name = "sb-randomness" 4 | version = "0.1.0" 5 | description = "Created with Anchor" 6 | edition = "2021" 7 | 8 | [lib] 9 | crate-type = ["cdylib", "lib"] 10 | name = "sb_randomness" 11 | 12 | [features] 13 | no-entrypoint = [] 14 | no-idl = [] 15 | no-log-ix-name = [] 16 | cpi = ["no-entrypoint"] 17 | default = [] 18 | idl-build = ["anchor-lang/idl-build", "switchboard-on-demand/idl-build"] 19 | 20 | [dependencies] 21 | anchor-lang = "0.31.1" 22 | solana-program = ">=2,<3" 23 | bytemuck = "1.15.0" 24 | switchboard-on-demand = { version = "0.10.0", features = ["anchor"] } 25 | 26 | -------------------------------------------------------------------------------- /common/variable-overrides/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "variable-overrides", 3 | "version": "1.0.0", 4 | "description": "Chain-agnostic examples for Switchboard variable overrides", 5 | "type": "module", 6 | "scripts": { 7 | "test": "bun run testVariableOverrides.ts" 8 | }, 9 | "keywords": [ 10 | "switchboard", 11 | "oracle", 12 | "variable-overrides", 13 | "chain-agnostic" 14 | ], 15 | "author": "Switchboard", 16 | "license": "MIT", 17 | "dependencies": { 18 | "@switchboard-xyz/common": "^4.1.0", 19 | "dotenv": "^16.4.5" 20 | }, 21 | "devDependencies": { 22 | "@types/node": "^20.0.0", 23 | "typescript": "^5.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /solana/randomness/pancake-stacker/programs/pancake-stacker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | resolver = "2" 3 | name = "pancake-stacker" 4 | version = "0.1.0" 5 | description = "Pancake stacking game using Switchboard randomness" 6 | edition = "2021" 7 | 8 | [lib] 9 | crate-type = ["cdylib", "lib"] 10 | name = "pancake_stacker" 11 | 12 | [features] 13 | no-entrypoint = [] 14 | no-idl = [] 15 | no-log-ix-name = [] 16 | cpi = ["no-entrypoint"] 17 | default = [] 18 | idl-build = ["anchor-lang/idl-build", "switchboard-on-demand/idl-build"] 19 | 20 | [dependencies] 21 | anchor-lang = "0.31.1" 22 | solana-program = ">=2,<3" 23 | bytemuck = "1.15.0" 24 | switchboard-on-demand = { version = "0.10.0", features = ["anchor"] } 25 | -------------------------------------------------------------------------------- /evm/randomness/coin-flip/deploy/CoinFlip.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script, console} from "forge-std/Script.sol"; 5 | import {CoinFlip} from "../src/CoinFlip.sol"; 6 | 7 | contract CoinFlipScript is Script { 8 | 9 | address public constant SWITCHBOARD_ADDRESS = 0xB7F03eee7B9F56347e32cC71DaD65B303D5a0E67; 10 | 11 | CoinFlip public coinFlip; 12 | 13 | function setUp() public {} 14 | 15 | function run() public { 16 | vm.startBroadcast(); 17 | 18 | coinFlip = new CoinFlip(SWITCHBOARD_ADDRESS); 19 | 20 | console.log("CoinFlip contract deployed to:", address(coinFlip)); 21 | 22 | vm.stopBroadcast(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /evm/randomness/pancake-stacker/server.ts: -------------------------------------------------------------------------------- 1 | import { serve, file } from "bun"; 2 | import { join } from "path"; 3 | 4 | const PORT = process.env.PORT || 3000; 5 | 6 | serve({ 7 | port: PORT, 8 | async fetch(req) { 9 | const url = new URL(req.url); 10 | let path = url.pathname; 11 | 12 | // Default to index.html 13 | if (path === "/") { 14 | path = "/index.html"; 15 | } 16 | 17 | const filePath = join(import.meta.dir, "ui", path); 18 | const f = file(filePath); 19 | 20 | if (await f.exists()) { 21 | return new Response(f); 22 | } 23 | 24 | return new Response("Not Found", { status: 404 }); 25 | }, 26 | }); 27 | 28 | console.log(`Pancake Stacker UI running at http://localhost:${PORT}`); 29 | 30 | -------------------------------------------------------------------------------- /solana/randomness/pancake-stacker/server.ts: -------------------------------------------------------------------------------- 1 | import { serve, file } from "bun"; 2 | import { join } from "path"; 3 | 4 | const PORT = process.env.PORT || 3000; 5 | 6 | serve({ 7 | port: PORT, 8 | async fetch(req) { 9 | const url = new URL(req.url); 10 | let path = url.pathname; 11 | 12 | // Default to index.html 13 | if (path === "/") { 14 | path = "/index.html"; 15 | } 16 | 17 | const filePath = join(import.meta.dir, "ui", path); 18 | const f = file(filePath); 19 | 20 | if (await f.exists()) { 21 | return new Response(f); 22 | } 23 | 24 | return new Response("Not Found", { status: 404 }); 25 | }, 26 | }); 27 | 28 | console.log(`Pancake Stacker UI (Solana) running at http://localhost:${PORT}`); 29 | -------------------------------------------------------------------------------- /evm/price-feeds/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ES2022", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | // Some stricter flags (disabled by default) 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noPropertyAccessFromIndexSignature": false 26 | } 27 | } -------------------------------------------------------------------------------- /solana/surge/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "surge-streaming-example", 3 | "version": "1.0.0", 4 | "description": "Real-time WebSocket streaming of signed Switchboard oracle prices", 5 | "type": "module", 6 | "scripts": { 7 | "start": "ts-node scripts/runSurge.ts", 8 | "stream": "ts-node scripts/runSurge.ts" 9 | }, 10 | "author": "Switchboard Labs", 11 | "license": "MIT", 12 | "dependencies": { 13 | "@coral-xyz/anchor": "^0.31.1", 14 | "@solana/web3.js": "^1.98.0", 15 | "@switchboard-xyz/common": "^5.3.1", 16 | "@switchboard-xyz/on-demand": "^3.7.3", 17 | "yargs": "^17.7.2" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^22.13.10", 21 | "@types/yargs": "^17.0.33", 22 | "ts-node": "^10.9.2", 23 | "typescript": "^4.9.5" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /common/twitter-follower-count/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@switchboard-xyz/twitter-follower-example", 3 | "version": "1.0.0", 4 | "description": "Example of fetching Twitter/X follower count using Switchboard oracles with OAuth 2.0", 5 | "type": "module", 6 | "main": "getFollowerCount.ts", 7 | "scripts": { 8 | "start": "tsx getFollowerCount.ts", 9 | "test": "tsx getFollowerCount.ts" 10 | }, 11 | "keywords": [ 12 | "twitter", 13 | "x", 14 | "oracle", 15 | "switchboard", 16 | "social-media", 17 | "follower-count" 18 | ], 19 | "author": "Switchboard", 20 | "license": "MIT", 21 | "dependencies": { 22 | "@switchboard-xyz/common": "^5.2.2" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^22.13.10", 26 | "tsx": "^4.7.0", 27 | "typescript": "^4.9.5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solana/feeds/basic/programs/basic-oracle-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "basic-oracle-example" 3 | version = "0.1.0" 4 | description = "Basic Switchboard On-Demand Oracle Integration Example" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "basic_oracle_example" 10 | 11 | [features] 12 | default = [] 13 | no-entrypoint = [] 14 | no-idl = [] 15 | no-log-ix-name = [] 16 | cpi = ["no-entrypoint"] 17 | idl-build = ["anchor-lang/idl-build", "switchboard-on-demand/idl-build"] 18 | anchor-debug = [] 19 | 20 | [dependencies] 21 | anchor-lang = "0.31.1" 22 | switchboard-on-demand = { version = "0.10.3", features = ["anchor", "devnet"] } 23 | bytemuck = "1.14" 24 | 25 | [dev-dependencies] 26 | litesvm = "=0.6.1" 27 | solana-sdk = "2.1" # Match litesvm's version 28 | hex = "0.4" 29 | openssl = { version = "0.10.74", features = ["vendored"] } 30 | -------------------------------------------------------------------------------- /solana/prediction-market/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prediction-market-example", 3 | "version": "1.0.0", 4 | "description": "Switchboard integration for prediction market feeds (Kalshi)", 5 | "type": "module", 6 | "scripts": { 7 | "start": "ts-node scripts/testKalshiFeedVerification.ts", 8 | "test": "ts-node scripts/testKalshiFeedVerification.ts", 9 | "build": "cargo build-sbf" 10 | }, 11 | "author": "Switchboard Labs", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@coral-xyz/anchor": "^0.31.1", 15 | "@solana/web3.js": "^1.98.0", 16 | "@switchboard-xyz/common": "^5.3.1", 17 | "@switchboard-xyz/on-demand": "^3.7.3", 18 | "yargs": "^17.7.2" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^22.13.10", 22 | "@types/yargs": "^17.0.33", 23 | "ts-node": "^10.9.2", 24 | "typescript": "^4.9.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /solana/legacy/variable-overrides/programs/sb-on-demand-solana/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use switchboard_on_demand::on_demand::accounts::pull_feed::PullFeedAccountData; 3 | 4 | declare_id!("CeJiXcRwxWmFoCMy1GozyGrt4SoAXB9HmFTA1JAfmqQT"); 5 | #[program] 6 | pub mod sb_on_demand_solana { 7 | use super::*; 8 | 9 | pub fn test<'a>(ctx: Context) -> Result<()> { 10 | let feed_account = ctx.accounts.feed.data.borrow(); 11 | // Docs at: https://switchboard-on-demand-rust-docs.web.app/on_demand/accounts/pull_feed/struct.PullFeedAccountData.html 12 | let feed = PullFeedAccountData::parse(feed_account).unwrap(); 13 | msg!("Feed Value: {:?}", feed.value()); 14 | Ok(()) 15 | } 16 | } 17 | 18 | #[derive(Accounts)] 19 | pub struct Test<'info> { 20 | /// CHECK: via switchboard sdk 21 | pub feed: AccountInfo<'info>, 22 | } -------------------------------------------------------------------------------- /solana/feeds/advanced/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "advanced-oracle-example", 3 | "version": "1.0.0", 4 | "description": "Advanced Switchboard oracle integration with Pinocchio and ALT optimization", 5 | "type": "module", 6 | "scripts": { 7 | "start": "ts-node scripts/runUpdate.ts", 8 | "update": "ts-node scripts/runUpdate.ts", 9 | "build": "cargo build-sbf", 10 | "test": "cargo test" 11 | }, 12 | "author": "Switchboard Labs", 13 | "license": "MIT", 14 | "dependencies": { 15 | "@coral-xyz/anchor": "^0.31.1", 16 | "@solana/web3.js": "^1.98.0", 17 | "@switchboard-xyz/common": "^5.3.1", 18 | "@switchboard-xyz/on-demand": "^3.7.3", 19 | "yargs": "^17.7.2" 20 | }, 21 | "devDependencies": { 22 | "@types/node": "^22.13.10", 23 | "@types/yargs": "^17.0.33", 24 | "ts-node": "^10.9.2", 25 | "typescript": "^4.9.5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /solana/prediction-market/programs/prediction-market/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prediction-market" 3 | version = "0.1.0" 4 | description = "Prediction Market Oracle Integration Example" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "prediction_market" 10 | 11 | [features] 12 | default = [] 13 | no-entrypoint = [] 14 | no-idl = [] 15 | no-log-ix-name = [] 16 | cpi = ["no-entrypoint"] 17 | idl-build = ["anchor-lang/idl-build", "switchboard-on-demand/idl-build"] 18 | anchor-debug = [] 19 | 20 | [dependencies] 21 | anchor-lang = "0.31.1" 22 | switchboard-on-demand = { version = "0.10.0", features = ["anchor", "devnet"] } 23 | switchboard-protos = { version = "^0.2.1", features = ["serde"] } 24 | bytemuck = "1.14" 25 | serde_json = { version = "1.0", features = ["preserve_order"] } 26 | sha2 = "0.10" 27 | prost = "0.13" 28 | solana-program = "3.0.0" 29 | faster-hex = "0.10.0" 30 | -------------------------------------------------------------------------------- /evm/randomness/pancake-stacker/deploy/PancakeStacker.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script, console} from "forge-std/Script.sol"; 5 | import {PancakeStacker} from "../src/PancakeStacker.sol"; 6 | 7 | contract PancakeStackerScript is Script { 8 | 9 | // Mainnet: 0xB7F03eee7B9F56347e32cC71DaD65B303D5a0E67 10 | // Testnet: 0xD3860E2C66cBd5c969Fa7343e6912Eff0416bA33 11 | address public constant SWITCHBOARD_ADDRESS = 0xB7F03eee7B9F56347e32cC71DaD65B303D5a0E67; 12 | 13 | PancakeStacker public pancakeStacker; 14 | 15 | function setUp() public {} 16 | 17 | function run() public { 18 | vm.startBroadcast(); 19 | 20 | pancakeStacker = new PancakeStacker(SWITCHBOARD_ADDRESS); 21 | 22 | console.log("PancakeStacker contract deployed to:", address(pancakeStacker)); 23 | 24 | vm.stopBroadcast(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /solana/legacy/feeds/README.md: -------------------------------------------------------------------------------- 1 | # Legacy Feed Scripts 2 | 3 | This directory contains deprecated feed scripts that are maintained for compatibility but are no longer recommended for new projects. 4 | 5 | ## Deprecation Notice 6 | 7 | Support will permanently continue, but for new projects use `../basic/managedUpdate.ts` instead, which provides: 8 | - 90% lower costs through quote aggregation 9 | - Better performance with reduced network calls 10 | - Simplified implementation with fewer moving parts 11 | - Active maintenance and ongoing improvements 12 | 13 | ## Scripts in This Directory 14 | 15 | ### runFeed.ts - Individual Feed Updates 16 | 17 | **Status**: Legacy - Use `../basic/managedUpdate.ts` instead 18 | 19 | **Purpose**: Update specific pull feed accounts with detailed oracle response visibility. 20 | 21 | **Usage**: 22 | ```bash 23 | ts-node runFeed.ts --feed GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR 24 | ``` 25 | -------------------------------------------------------------------------------- /solana/feeds/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic-oracle-example", 3 | "version": "1.0.0", 4 | "description": "Basic Switchboard oracle integration using managed feeds", 5 | "type": "module", 6 | "scripts": { 7 | "start": "ts-node scripts/managedUpdate.ts", 8 | "update": "ts-node scripts/managedUpdate.ts", 9 | "create-feed": "ts-node scripts/createManagedFeed.ts", 10 | "build": "cargo build-sbf", 11 | "test": "cargo test" 12 | }, 13 | "author": "Switchboard Labs", 14 | "license": "MIT", 15 | "dependencies": { 16 | "@coral-xyz/anchor": "^0.31.1", 17 | "@solana/web3.js": "^1.98.0", 18 | "@switchboard-xyz/common": "^5.3.1", 19 | "@switchboard-xyz/on-demand": "^3.7.3", 20 | "yargs": "^17.7.2" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "^22.13.10", 24 | "@types/yargs": "^17.0.33", 25 | "ts-node": "^10.9.2", 26 | "typescript": "^4.9.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /evm/randomness/coin-flip/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | env: 9 | FOUNDRY_PROFILE: ci 10 | 11 | jobs: 12 | check: 13 | strategy: 14 | fail-fast: true 15 | 16 | name: Foundry project 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: recursive 22 | 23 | - name: Install Foundry 24 | uses: foundry-rs/foundry-toolchain@v1 25 | 26 | - name: Show Forge version 27 | run: | 28 | forge --version 29 | 30 | - name: Run Forge fmt 31 | run: | 32 | forge fmt --check 33 | id: fmt 34 | 35 | - name: Run Forge build 36 | run: | 37 | forge build --sizes 38 | id: build 39 | 40 | - name: Run Forge tests 41 | run: | 42 | forge test -vvv 43 | id: test 44 | -------------------------------------------------------------------------------- /evm/randomness/pancake-stacker/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | env: 9 | FOUNDRY_PROFILE: ci 10 | 11 | jobs: 12 | check: 13 | strategy: 14 | fail-fast: true 15 | 16 | name: Foundry project 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: recursive 22 | 23 | - name: Install Foundry 24 | uses: foundry-rs/foundry-toolchain@v1 25 | 26 | - name: Show Forge version 27 | run: | 28 | forge --version 29 | 30 | - name: Run Forge fmt 31 | run: | 32 | forge fmt --check 33 | id: fmt 34 | 35 | - name: Run Forge build 36 | run: | 37 | forge build --sizes 38 | id: build 39 | 40 | - name: Run Forge tests 41 | run: | 42 | forge test -vvv 43 | id: test 44 | -------------------------------------------------------------------------------- /solana/x402/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "x402-paywalled-data-example", 3 | "version": "1.0.0", 4 | "description": "Switchboard integration with X402 micropayment protocol for paywalled data sources", 5 | "type": "module", 6 | "scripts": { 7 | "start": "ts-node scripts/x402Update.ts" 8 | }, 9 | "author": "Switchboard Labs", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@coral-xyz/anchor": "^0.31.1", 13 | "@faremeter/payment-solana": "^0.8.0", 14 | "@faremeter/wallet-solana": "^0.8.0", 15 | "@solana/spl-token": "^0.4.13", 16 | "@solana/web3.js": "^1.98.0", 17 | "@switchboard-xyz/common": "^5.3.1", 18 | "@switchboard-xyz/on-demand": "^3.7.3", 19 | "@switchboard-xyz/x402-utils": "0.3.0", 20 | "yargs": "^17.7.2" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "^22.13.10", 24 | "@types/yargs": "^17.0.33", 25 | "ts-node": "^10.9.2", 26 | "typescript": "^4.9.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /solana/legacy/variable-overrides/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "variable-overrides", 3 | "version": "1.0.0", 4 | "description": "Switchboard On-Demand Variable Overrides Example", 5 | "main": "index.js", 6 | "scripts": { 7 | "pull": "bun run scripts/pull.ts", 8 | "test-simple": "bun run scripts/test-simple.ts", 9 | "test-multi-auth": "bun run scripts/test-multi-auth.ts" 10 | }, 11 | "keywords": [ 12 | "switchboard", 13 | "oracle", 14 | "solana", 15 | "on-demand", 16 | "variable-overrides" 17 | ], 18 | "author": "Switchboard", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "@types/node": "^20.0.0", 22 | "typescript": "^5.0.0" 23 | }, 24 | "dependencies": { 25 | "@coral-xyz/anchor": "^0.30.1", 26 | "@solana/web3.js": "^1.95.2", 27 | "@switchboard-xyz/common": "^4.1.0", 28 | "@switchboard-xyz/on-demand": "^2.16.3", 29 | "dotenv": "^16.4.5", 30 | "js-sha256": "^0.11.0", 31 | "tweetnacl": "^1.0.3" 32 | } 33 | } -------------------------------------------------------------------------------- /solana/randomness/pancake-stacker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pancake-stacker", 3 | "version": "1.0.0", 4 | "description": "Pancake stacking game using Switchboard randomness", 5 | "type": "module", 6 | "scripts": { 7 | "start": "bun run server.ts", 8 | "flip": "NODE_OPTIONS='--no-warnings' ts-node scripts/index.ts", 9 | "build": "cargo build-sbf", 10 | "test": "cargo test" 11 | }, 12 | "devDependencies": { 13 | "@types/bun": "latest", 14 | "@types/readline-sync": "^1.4.8", 15 | "@types/yargs": "^17.0.33", 16 | "prettier": "^3.3.3", 17 | "ts-node": "^10.9.2" 18 | }, 19 | "peerDependencies": { 20 | "typescript": "^5.0.0" 21 | }, 22 | "dependencies": { 23 | "@coral-xyz/anchor": "^0.31.1", 24 | "@solana/spl-token": "^0.4.9", 25 | "@solana/web3.js": "^1.98.2", 26 | "@switchboard-xyz/common": "^5.2.2", 27 | "@switchboard-xyz/on-demand": "^3.2.0", 28 | "readline-sync": "^1.4.10", 29 | "tsx": "^4.19.1", 30 | "yargs": "^17.7.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /solana/randomness/coin-flip/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "randomness-example", 3 | "version": "1.0.0", 4 | "description": "Switchboard randomness integration for on-chain games", 5 | "type": "module", 6 | "scripts": { 7 | "start": "NODE_OPTIONS='--no-warnings' ts-node scripts/index.ts", 8 | "start-eclipse": "NODE_OPTIONS='--no-warnings' ts-node scripts/eclipse.ts", 9 | "build": "cargo build-sbf", 10 | "test": "cargo test" 11 | }, 12 | "devDependencies": { 13 | "@types/bun": "latest", 14 | "@types/readline-sync": "^1.4.8", 15 | "@types/yargs": "^17.0.33", 16 | "prettier": "^3.3.3", 17 | "ts-node": "^10.9.2" 18 | }, 19 | "peerDependencies": { 20 | "typescript": "^5.0.0" 21 | }, 22 | "dependencies": { 23 | "@coral-xyz/anchor": "^0.31.1", 24 | "@solana/spl-token": "^0.4.9", 25 | "@solana/web3.js": "^1.98.2", 26 | "@switchboard-xyz/common": "^5.2.2", 27 | "@switchboard-xyz/on-demand": "^3.2.0", 28 | "readline-sync": "^1.4.10", 29 | "tsx": "^4.19.1", 30 | "yargs": "^17.7.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /common/rust-feed-creation/README.md: -------------------------------------------------------------------------------- 1 | # Rust Feed Creation Example 2 | 3 | This project demonstrates how to create and simulate Switchboard On-Demand feeds using the Rust `CrossbarClient`. It includes examples for both HTTP fetching and static value tasks, which can be used to verify feed configurations before integrating with the Vulcan Forge API. 4 | 5 | ## Prerequisites 6 | 7 | - Rust (latest stable) 8 | - Internet connection (to reach `https://crossbar.switchboard.xyz`) 9 | 10 | ## Usage 11 | 12 | ### Run the Example 13 | The main program creates a BTC/USD feed, stores it, simulates it, and prints a JSON payload suitable for the Vulcan Forge API. 14 | 15 | ```bash 16 | cargo run 17 | ``` 18 | 19 | ### Run the Tests 20 | The tests verify both an HTTP task (fetching live BTC price) and a static Value task (returning a constant). 21 | 22 | ```bash 23 | cargo test -- --nocapture 24 | ``` 25 | 26 | ## Code Structure 27 | 28 | - `src/main.rs`: Contains the logic. 29 | - `main()`: Runs the example flow. 30 | - `tests` module: Contains integration tests that hit the live Crossbar endpoints. 31 | 32 | -------------------------------------------------------------------------------- /sui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sui-switchboard-examples", 3 | "version": "1.0.0", 4 | "description": "Sui examples for Switchboard On-Demand functionality with Quote Verifier", 5 | "type": "module", 6 | "scripts": { 7 | "quotes": "tsx examples/quotes.ts", 8 | "surge": "tsx examples/surge.ts", 9 | "example": "tsx scripts/run.ts", 10 | "build": "sui move build", 11 | "build:testnet": "cp Move.testnet.toml Move.toml && sui move build", 12 | "build:mainnet": "cp Move.toml.bak Move.toml && sui move build || cp Move.toml.bak Move.toml", 13 | "test": "sui move test", 14 | "deploy": "sui client publish --gas-budget 100000000", 15 | "deploy:testnet": "cp Move.testnet.toml Move.toml && sui client publish --gas-budget 100000000", 16 | "deploy:mainnet": "sui client publish --gas-budget 100000000" 17 | }, 18 | "keywords": [ 19 | "switchboard", 20 | "oracle", 21 | "sui", 22 | "defi", 23 | "price-feeds", 24 | "quote-verifier" 25 | ], 26 | "author": "Switchboard", 27 | "license": "MIT", 28 | "dependencies": { 29 | "@mysten/sui": "^1.38.0", 30 | "@switchboard-xyz/on-demand": "^1.0.0", 31 | "@switchboard-xyz/sui-sdk": "^0.1.14", 32 | "yargs": "^17.7.2" 33 | }, 34 | "devDependencies": { 35 | "@types/node": "^20.0.0", 36 | "@types/yargs": "^17.0.32", 37 | "tsx": "^4.0.0", 38 | "typescript": "^5.0.0" 39 | } 40 | } -------------------------------------------------------------------------------- /common/streaming/crossbarStream.ts: -------------------------------------------------------------------------------- 1 | import * as sb from "@switchboard-xyz/on-demand"; 2 | 3 | (async function main() { 4 | const apiKey = process.env.SURGE_API_KEY!; 5 | 6 | const surge = new sb.Surge({ 7 | apiKey: apiKey, 8 | crossbarUrl: "https://crossbar.switchboardlabs.xyz", 9 | crossbarMode: true, 10 | verbose: true, 11 | }); 12 | 13 | // Listen for unsigned price updates 14 | surge.on("unsignedPriceUpdate", (update: sb.UnsignedPriceUpdate) => { 15 | const symbols = update.getSymbols(); 16 | const sources = update.getSources(); 17 | const formattedPrices = update.getFormattedPrices(); 18 | // Uncomment the line below to see the full update object 19 | // console.log(`\nReceived unsigned price update for ${JSON.stringify(update)}`); 20 | 21 | const rawResponse = update.getRawResponse(); 22 | rawResponse.feed_values.forEach((feedValue: any) => { 23 | const symbol = feedValue.symbol; 24 | const latency = Date.now() - feedValue.seen_at_ts_ms; 25 | console.log(`\nReceived unsigned price update for ${symbol}:`); 26 | const latencyInfo = ` | Latency: ${latency}ms`; 27 | console.log( 28 | `${symbol} (${sources[0]}): ${formattedPrices[symbol]}${latencyInfo}` 29 | ); 30 | }); 31 | }); 32 | 33 | // Connect and subscribe 34 | await surge.connectAndSubscribe([ 35 | { symbol: "2Z/USD" }, 36 | ]); 37 | console.log("🎧 Streaming prices...\n"); 38 | })(); 39 | -------------------------------------------------------------------------------- /evm/price-feeds/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "price-consumer-example", 3 | "version": "2.0.0", 4 | "description": "Switchboard price feed consumer example for EVM chains", 5 | "type": "module", 6 | "scripts": { 7 | "example": "bun scripts/run.ts", 8 | "surge-convert": "bun scripts/surgeToEvmConversion.ts", 9 | "build": "forge build", 10 | "test": "forge test", 11 | "deploy": "forge script deploy/DeploySwitchboardPriceConsumer.s.sol:DeploySwitchboardPriceConsumer --broadcast -vvvv", 12 | "deploy:monad-testnet": "forge script deploy/DeploySwitchboardPriceConsumer.s.sol:DeploySwitchboardPriceConsumer --rpc-url https://testnet.monad.xyz --broadcast -vvvv", 13 | "deploy:monad-mainnet": "forge script deploy/DeploySwitchboardPriceConsumer.s.sol:DeploySwitchboardPriceConsumer --rpc-url $MONAD_RPC_URL --broadcast -vvvv", 14 | "verify": "forge verify-contract --chain-id $CHAIN_ID --watch $CONTRACT_ADDRESS src/SwitchboardPriceConsumer.sol:SwitchboardPriceConsumer" 15 | }, 16 | "keywords": [ 17 | "switchboard", 18 | "oracle", 19 | "evm", 20 | "ethereum", 21 | "monad", 22 | "arbitrum", 23 | "defi", 24 | "price-feeds" 25 | ], 26 | "author": "Switchboard Labs", 27 | "license": "MIT", 28 | "devDependencies": { 29 | "@types/bun": "latest", 30 | "@types/node": "^20.0.0" 31 | }, 32 | "peerDependencies": { 33 | "typescript": "^5.0.0" 34 | }, 35 | "dependencies": { 36 | "@switchboard-xyz/common": "^5.5.1", 37 | "@switchboard-xyz/on-demand-solidity": "^0.0.4", 38 | "@types/yargs": "^17.0.33", 39 | "ethers": "^6.13.1", 40 | "yargs": "^18.0.0" 41 | } 42 | } -------------------------------------------------------------------------------- /common/job-testing/runJob.ts: -------------------------------------------------------------------------------- 1 | import { OracleJob, CrossbarClient } from "@switchboard-xyz/common"; 2 | 3 | function buildCoinbaseJob(pair: string): OracleJob { 4 | const parts = pair.split("-"); 5 | const jobConfig = { 6 | tasks: [ 7 | { 8 | valueTask: { value: 1 }, 9 | }, 10 | { 11 | divideTask: { 12 | job: { 13 | tasks: [ 14 | { 15 | httpTask: { 16 | url: `https://api.coinbase.com/v2/exchange-rates?currency=${parts[1]}`, 17 | headers: [ 18 | { key: "Accept", value: "application/json" }, 19 | { key: "User-Agent", value: "Mozilla/5.0" }, 20 | ], 21 | }, 22 | }, 23 | { 24 | jsonParseTask: { 25 | path: `$.data.rates.${parts[0]}`, 26 | }, 27 | }, 28 | ], 29 | }, 30 | }, 31 | }, 32 | ], 33 | }; 34 | return OracleJob.fromObject(jobConfig); 35 | } 36 | 37 | (async function main() { 38 | // Get pair from command line args or use default 39 | const pair = "BTC-USD"; 40 | 41 | // Initialize Crossbar client (chain-agnostic) 42 | const crossbarUrl = "https://crossbar.switchboard.xyz"; 43 | const crossbarClient = new CrossbarClient(crossbarUrl); 44 | 45 | console.log(`\n📊 Fetching ${pair} price from Coinbase...\n`); 46 | 47 | // Create an OracleFeed with the job 48 | const feed = { 49 | name: `${pair} Price - Coinbase`, 50 | jobs: [buildCoinbaseJob(pair)], 51 | }; 52 | 53 | // Simulate feed with Crossbar 54 | const result = await crossbarClient.simulateFeed( 55 | feed, 56 | false, // includeReceipts 57 | ); 58 | 59 | // Display results 60 | const price = result.results?.[0]; 61 | 62 | console.log(`✅ ${pair} Price: $${Number(price).toLocaleString()}`); 63 | console.log(JSON.stringify(result, null, 2)); 64 | })(); 65 | -------------------------------------------------------------------------------- /solana/legacy/variable-overrides/scripts/test-simple.ts: -------------------------------------------------------------------------------- 1 | import { CrossbarClient } from "@switchboard-xyz/common"; 2 | import { buildSimpleValueJob } from "./utils/utils"; 3 | import * as sb from "@switchboard-xyz/on-demand"; 4 | import dotenv from "dotenv"; 5 | 6 | /** 7 | * Simple test script demonstrating variable overrides with a value task 8 | * Run with: TEST_VALUE=12345 bun run scripts/test-simple.ts 9 | */ 10 | 11 | const crossbarClient = new CrossbarClient( 12 | "https://crossbar.switchboard.xyz", 13 | /* verbose= */ true 14 | ); 15 | 16 | (async function main() { 17 | try { 18 | dotenv.config(); 19 | 20 | const testValue = process.env.TEST_VALUE || "100"; 21 | console.log(`\n🧪 Testing Variable Overrides with value: ${testValue}`); 22 | 23 | const { keypair, connection } = await sb.AnchorUtils.loadEnv(); 24 | const queueAccount = await sb.getDefaultQueue(connection.rpcEndpoint); 25 | const queue = queueAccount.pubkey; 26 | 27 | // Create job with variable override 28 | const job = buildSimpleValueJob(); 29 | console.log("\n📋 Job Definition:", JSON.stringify(job.toJSON(), null, 2)); 30 | 31 | // Test the variable override functionality 32 | const feedConfigs = [{ 33 | feed: { 34 | jobs: [job], 35 | }, 36 | }]; 37 | 38 | console.log("\n🔧 Variable Overrides:", { 39 | TEST_VALUE: testValue 40 | }); 41 | 42 | // Note: In a real implementation, you would use this with queue.fetchSignaturesConsensus 43 | // This is a simplified test to demonstrate the job structure 44 | console.log("\n✅ Variable override job created successfully!"); 45 | console.log(" - Job uses ${TEST_VALUE} placeholder"); 46 | console.log(` - Will be replaced with: ${testValue}`); 47 | console.log(" - No secrets management required"); 48 | 49 | } catch (error) { 50 | console.error("Error during test:", error); 51 | process.exit(1); 52 | } 53 | 54 | process.exit(0); 55 | })(); -------------------------------------------------------------------------------- /common/twitter-follower-count/oauth.ts: -------------------------------------------------------------------------------- 1 | import * as readline from "readline"; 2 | 3 | /** 4 | * Prompt user to paste a token manually (hidden input) 5 | */ 6 | export async function promptForToken(): Promise { 7 | const rl = readline.createInterface({ 8 | input: process.stdin, 9 | output: process.stdout, 10 | terminal: true, 11 | }); 12 | 13 | return new Promise((resolve) => { 14 | let muted = false; 15 | 16 | // Override _writeToOutput to hide characters 17 | const oldWriteToOutput = (rl as any)._writeToOutput; 18 | (rl as any)._writeToOutput = function _writeToOutput(stringToWrite: string) { 19 | if (muted) { 20 | // Don't show the characters being typed/pasted 21 | return; 22 | } 23 | oldWriteToOutput.apply(rl, [stringToWrite]); 24 | }; 25 | 26 | rl.question("📋 Paste your Bearer Token here (hidden): ", (token) => { 27 | // Restore normal output 28 | muted = false; 29 | (rl as any)._writeToOutput = oldWriteToOutput; 30 | rl.close(); 31 | 32 | const trimmed = token.trim(); 33 | if (trimmed) { 34 | const length = trimmed.length; 35 | console.log(`\n✓ Token received (${length} characters)\n`); 36 | } else { 37 | console.log("\n⚠️ No token provided\n"); 38 | } 39 | resolve(trimmed); 40 | }); 41 | 42 | // Start muting after the prompt is displayed 43 | muted = true; 44 | }); 45 | } 46 | 47 | /** 48 | * Get Bearer Token from user 49 | */ 50 | export async function getAccessToken(): Promise { 51 | console.log("\n╔════════════════════════════════════════════════════════════╗"); 52 | console.log("║ Twitter Bearer Token Authentication Required ║"); 53 | console.log("╚════════════════════════════════════════════════════════════╝\n"); 54 | console.log("📖 How to get your Bearer Token:\n"); 55 | console.log("1. Go to: https://developer.twitter.com/en/portal/projects-and-apps"); 56 | console.log("2. Select your app (or create one if you don't have one)"); 57 | console.log("3. Click on: Keys and tokens"); 58 | console.log("4. Under 'Authentication Tokens' find 'Bearer Token'"); 59 | console.log("5. Click 'Generate' (or 'Regenerate' if it already exists)"); 60 | console.log("6. Copy the Bearer Token\n"); 61 | 62 | return await promptForToken(); 63 | } 64 | -------------------------------------------------------------------------------- /solana/randomness/coin-flip/scripts/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![Switchboard Logo](https://github.com/switchboard-xyz/core-sdk/raw/main/website/static/img/icons/switchboard/avatar.png) 4 | 5 | # Switchboard On-Demand: Randomness 6 | This example demonstrates the use of Switchboard's On-Demand Randomness functionality. 7 | 8 |
9 | 10 | 11 | ## Getting Started 12 | 13 | Welcome to Switchboard randomness on-demand. The easiest and most secure 14 | randomness solution on Solana. 15 | 16 | To read more about the adversarial assumptions that Switchboard Randomness 17 | On-Demand makes, please see: [https://docs.switchboard.xyz/docs/switchboard/switchboard-randomness](https://docs.switchboard.xyz/docs/switchboard/switchboard-randomness) 18 | 19 | #### PLEASE ENSURE YOU USE ANCHOR VERSION 0.31.1. 20 | 21 | Configure the `anchor.toml` file to point to your solana wallet and the Solana cluster of your choice - Devnet, Mainnet, etc. 22 | 23 | Then, to see the power of on-demand feeds, run the following: 24 | 25 | ```bash 26 | cd .. 27 | anchor build 28 | ``` 29 | After building, take note of your program address and insert it in your program `lib.rs` file here: 30 | *Note:* an easy command to view your recently built program address - `anchor keys list`. 31 | ```rust 32 | declare_id!(“[YOUR_PROGRAM_ADDRESS]“); 33 | ``` 34 | Rebuild your program. 35 | ```bash 36 | anchor build 37 | ``` 38 | Deploy your program, initialise the IDL. 39 | *Note:* ensure you insert your program address in the IDL initialise command. 40 | 41 | ```bash 42 | anchor deploy 43 | anchor idl init --filepath target/idl/sb_randomness.json YOUR_PROGRAM_ADDRESS 44 | ``` 45 | Install deps: 46 | ```bash 47 | cd solana/examples/randomness 48 | pnpm i 49 | pnpm update 50 | ``` 51 | 52 | ## Running the example 53 | 54 | This example repo quickly demonstrates how to use Switchboard randomness. 55 | 56 | Its as simple as running one command via our sdk to commit to using a slothash, 57 | requesting the entropy from the oracle, and revealing said randomness. 58 | 59 | Randomness is demonstrated through the simple Coin Flip Challenge game! You must guess the outcome of a coin-flip, either "heads" or "tails", and let Switchboards randomness do the rest - hopefully you win! 60 | 61 | To run this example coin-flip game: 62 | 63 | `pnpm start {YOUR_GUESS}` 64 | 65 | 66 | For a full explanation of the code, please see our gitbook tutorial [here!](https://docs.switchboard.xyz/docs/switchboard/switchboard-randomness/getting-started) 67 | -------------------------------------------------------------------------------- /sui/Move.lock: -------------------------------------------------------------------------------- 1 | # @generated by Move, please check-in and do not edit manually. 2 | 3 | [move] 4 | version = 3 5 | manifest_digest = "4CD73D761F2AAA03045DBF11DD1C17B3A4CBD42ED68FEA79ABE05523444EB05B" 6 | deps_digest = "397E6A9F7A624706DBDFEE056CE88391A15876868FD18A88504DA74EB458D697" 7 | dependencies = [ 8 | { id = "Bridge", name = "Bridge" }, 9 | { id = "MoveStdlib", name = "MoveStdlib" }, 10 | { id = "Sui", name = "Sui" }, 11 | { id = "SuiSystem", name = "SuiSystem" }, 12 | { id = "Switchboard", name = "Switchboard" }, 13 | ] 14 | 15 | [[move.package]] 16 | id = "Bridge" 17 | source = { git = "https://github.com/MystenLabs/sui.git", rev = "4e8b6eda7d6411d80c62f39ac8a4f028e8d174c4", subdir = "crates/sui-framework/packages/bridge" } 18 | 19 | dependencies = [ 20 | { id = "MoveStdlib", name = "MoveStdlib" }, 21 | { id = "Sui", name = "Sui" }, 22 | { id = "SuiSystem", name = "SuiSystem" }, 23 | ] 24 | 25 | [[move.package]] 26 | id = "MoveStdlib" 27 | source = { git = "https://github.com/MystenLabs/sui.git", rev = "4e8b6eda7d6411d80c62f39ac8a4f028e8d174c4", subdir = "crates/sui-framework/packages/move-stdlib" } 28 | 29 | [[move.package]] 30 | id = "Sui" 31 | source = { git = "https://github.com/MystenLabs/sui.git", rev = "4e8b6eda7d6411d80c62f39ac8a4f028e8d174c4", subdir = "crates/sui-framework/packages/sui-framework" } 32 | 33 | dependencies = [ 34 | { id = "MoveStdlib", name = "MoveStdlib" }, 35 | ] 36 | 37 | [[move.package]] 38 | id = "SuiSystem" 39 | source = { git = "https://github.com/MystenLabs/sui.git", rev = "4e8b6eda7d6411d80c62f39ac8a4f028e8d174c4", subdir = "crates/sui-framework/packages/sui-system" } 40 | 41 | dependencies = [ 42 | { id = "MoveStdlib", name = "MoveStdlib" }, 43 | { id = "Sui", name = "Sui" }, 44 | ] 45 | 46 | [[move.package]] 47 | id = "Switchboard" 48 | source = { git = "https://github.com/switchboard-xyz/sui.git", rev = "testnet", subdir = "on_demand" } 49 | 50 | dependencies = [ 51 | { id = "Bridge", name = "Bridge" }, 52 | { id = "MoveStdlib", name = "MoveStdlib" }, 53 | { id = "Sui", name = "Sui" }, 54 | { id = "SuiSystem", name = "SuiSystem" }, 55 | ] 56 | 57 | [move.toolchain-version] 58 | compiler-version = "1.58.1" 59 | edition = "2024.beta" 60 | flavor = "sui" 61 | 62 | [env] 63 | 64 | [env.testnet] 65 | chain-id = "4c78adac" 66 | original-published-id = "0x4efaf0484726101564f7c275225f40b851c2e3196592fad41086790c10824376" 67 | latest-published-id = "0x4efaf0484726101564f7c275225f40b851c2e3196592fad41086790c10824376" 68 | published-version = "1" 69 | -------------------------------------------------------------------------------- /evm/price-feeds/deploy/DeploySwitchboardPriceConsumer.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.22; 3 | 4 | import "forge-std/Script.sol"; 5 | import "../src/SwitchboardPriceConsumer.sol"; 6 | 7 | /** 8 | * @title DeploySwitchboardPriceConsumer 9 | * @notice Deployment script for the SwitchboardPriceConsumer contract 10 | * 11 | * Usage: 12 | * # Monad Testnet 13 | * forge script script/DeploySwitchboardPriceConsumer.s.sol:DeploySwitchboardPriceConsumer \ 14 | * --rpc-url https://testnet-rpc.monad.xyz \ 15 | * --broadcast \ 16 | * --verify \ 17 | * -vvvv 18 | * 19 | * # Monad Mainnet 20 | * forge script script/DeploySwitchboardPriceConsumer.s.sol:DeploySwitchboardPriceConsumer \ 21 | * --rpc-url https://rpc-mainnet.monadinfra.com/rpc/YOUR_KEY \ 22 | * --broadcast \ 23 | * --verify \ 24 | * -vvvv 25 | * 26 | * # With environment variables 27 | * SWITCHBOARD_ADDRESS=0x... forge script script/DeploySwitchboardPriceConsumer.s.sol:DeploySwitchboardPriceConsumer \ 28 | * --rpc-url $RPC_URL \ 29 | * --broadcast \ 30 | * -vvvv 31 | */ 32 | contract DeploySwitchboardPriceConsumer is Script { 33 | // Monad Switchboard addresses 34 | address constant MONAD_TESTNET_SWITCHBOARD = 0xD3860E2C66cBd5c969Fa7343e6912Eff0416bA33; 35 | address constant MONAD_MAINNET_SWITCHBOARD = 0xB7F03eee7B9F56347e32cC71DaD65B303D5a0E67; 36 | 37 | function run() external { 38 | // Get Switchboard address from environment or use default (testnet) 39 | address switchboardAddress = vm.envOr("SWITCHBOARD_ADDRESS", MONAD_TESTNET_SWITCHBOARD); 40 | 41 | console.log("Deploying SwitchboardPriceConsumer..."); 42 | console.log("Switchboard address:", switchboardAddress); 43 | console.log("Deployer:", msg.sender); 44 | 45 | vm.startBroadcast(); 46 | 47 | SwitchboardPriceConsumer consumer = new SwitchboardPriceConsumer(switchboardAddress); 48 | 49 | vm.stopBroadcast(); 50 | 51 | console.log("SwitchboardPriceConsumer deployed at:", address(consumer)); 52 | console.log(""); 53 | console.log("Configuration:"); 54 | console.log(" Max Price Age:", consumer.maxPriceAge(), "seconds"); 55 | console.log(" Max Deviation:", consumer.maxDeviationBps(), "bps"); 56 | console.log(" Owner:", consumer.owner()); 57 | console.log(""); 58 | console.log("Next steps:"); 59 | console.log("1. Save the contract address"); 60 | console.log("2. Run: CONTRACT_ADDRESS=", address(consumer), " bun scripts/run.ts"); 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /evm/legacy/script/Deploy.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | // forge script script/Deploy.s.sol:DeployScript --rpc-url https://rpc.hyperliquid.xyz/evm --broadcast -vv 5 | 6 | import "forge-std/Script.sol"; 7 | import {Example} from "../src/Example.sol"; 8 | import {ISwitchboard} from "@switchboard-xyz/on-demand-solidity/ISwitchboard.sol"; 9 | import {Structs} from "@switchboard-xyz/on-demand-solidity/structs/Structs.sol"; 10 | 11 | contract DeployScript is Script { 12 | Example public example; 13 | 14 | function run() external { 15 | console.log("running deploy script"); 16 | 17 | // read env variables and choose EOA for transaction signing 18 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 19 | 20 | // Get the Aggregator ID 21 | bytes32 aggregatorId = vm.envBytes32("AGGREGATOR_ID"); 22 | 23 | // Get Switchboard address from environment variable 24 | // For networks addresses, see https://docs.switchboard.xyz/product-documentation/data-feeds/evm 25 | address switchboard = vm.envAddress("SWITCHBOARD_ADDRESS"); 26 | 27 | // Get Switchboard 28 | ISwitchboard sb = ISwitchboard(switchboard); 29 | // Get the Aggregator 30 | (Structs.Aggregator memory aggregator, ) = sb.getAggregator( 31 | aggregatorId 32 | ); 33 | 34 | // Log the Configs 35 | console.log("Authority:"); 36 | console.log(aggregator.authority); 37 | 38 | console.log("Name:"); 39 | console.log(aggregator.name); 40 | 41 | console.log("Queue ID:"); 42 | console.logBytes32(aggregator.queueId); 43 | 44 | console.log("Tolerated Delta:"); 45 | console.log(aggregator.toleratedDelta); 46 | 47 | console.log("CID:"); 48 | console.logBytes32(aggregator.cid); 49 | 50 | console.log("Feed Hash:"); 51 | console.logBytes32(aggregator.feedHash); 52 | 53 | console.log("Created At:"); 54 | console.log(aggregator.createdAt); 55 | 56 | console.log("Max Variance:"); 57 | console.log(aggregator.maxVariance); 58 | 59 | console.log("Min Responses:"); 60 | console.log(aggregator.minResponses); 61 | 62 | console.log("Min Samples:"); 63 | console.log(aggregator.minSamples); 64 | 65 | console.log("Max Staleness:"); 66 | console.log(aggregator.maxStaleness); 67 | 68 | vm.startBroadcast(deployerPrivateKey); 69 | example = new Example( 70 | switchboard, 71 | aggregatorId 72 | ); 73 | 74 | vm.setEnv("EXAMPLE_ADDRESS", vm.toString(address(example))); 75 | vm.stopBroadcast(); 76 | } 77 | } -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- 1 | # Repository Guidelines 2 | 3 | This repository collects Switchboard On-Demand examples across Solana, EVM, and Sui; keep changes small, repeatable, and runnable per chain. 4 | 5 | ## Project Structure & Module Organization 6 | - `common/` – chain-agnostic TypeScript/Bun utilities (job-testing, streaming, variable-overrides); scripts live in subfolders. 7 | - `solana/` – Anchor-style Rust program code in `programs/`, TS examples in `examples/`, streaming demos in `surge/`, build outputs in `target/`. 8 | - `evm/` – Foundry Solidity contracts in `src/`, deployment scripts under `script/`, TypeScript helpers in `examples/` and `scripts/`. 9 | - `sui/` – Move modules in `sources/`, transaction helpers in `scripts/`, TS samples in `examples/`; Move config in `Move.toml`. 10 | 11 | ## Build, Test, and Development Commands 12 | - Prereqs: Node 16+, Bun, pnpm, Rust toolchain/Cargo, Foundry (`forge`), Solana CLI, and Sui CLI. 13 | - Common TypeScript: `cd common && bun install && bun run stream` (Crossbar WebSocket), `bun run job` (job testing). 14 | - Solana: `cd solana && pnpm install && pnpm build` (BPF build), `pnpm test` (Rust unit/integration), `pnpm benchmark` for CU/latency checks. 15 | - EVM: `cd evm && bun install && bun run example` (read a feed), `forge build`, `forge test`, deploy with `pnpm deploy:monad-testnet` after setting RPC/keys. 16 | - Sui: `cd sui && pnpm install && pnpm run build` (Move build), `pnpm test` or `sui move test`, `pnpm run quotes` for TypeScript data pulls. 17 | 18 | ## Coding Style & Naming Conventions 19 | - TypeScript: ES modules, camelCase for functions/vars, PascalCase for classes; format with `pnpm lint` / `pnpm lint:fix` in `solana/` (Prettier defaults). Keep example files named by feature (e.g., `examples/randomness.ts`). 20 | - Rust: run `cargo fmt` and `cargo clippy` before commits; keep modules focused and tests near the code they cover. 21 | - Solidity/Move: align with Foundry/Sui defaults; use descriptive contract/module names (`SwitchboardPriceConsumer`, `quote_verifier`) and document public functions. 22 | 23 | ## Testing Guidelines 24 | - Place tests alongside chain folders (`evm/test`, Move tests in `sources/` with `#[test]` blocks, Solana Rust tests in `programs/*/src/tests`). 25 | - Favor deterministic inputs; stub external calls when possible. 26 | - Run chain suites before PRs: `pnpm test` in `solana/`, `forge test` in `evm/`, `sui move test` in `sui/`; capture sample output when behavior changes. 27 | 28 | ## Commit & Pull Request Guidelines 29 | - Commit messages follow short imperative style seen in history (e.g., “Fix randomness validation”); keep scope focused. 30 | - PRs should describe the chain touched, commands run, and any config changes; link issues and attach logs/screens where helpful (benchmarks, deploy receipts). 31 | - Document required env vars/secrets instead of embedding them; prefer the `common/variable-overrides` pattern for API keys. 32 | -------------------------------------------------------------------------------- /solana/feeds/advanced/scripts/README.md: -------------------------------------------------------------------------------- 1 | # Advanced Oracle Integration Examples 2 | 3 | This directory contains advanced examples demonstrating **Pinocchio Framework** integration for ultra-low compute unit oracle operations with Switchboard On-Demand. 4 | 5 | ## Framework Comparison 6 | 7 | - **Basic Examples**: Use Anchor Framework (~2k CU with overhead) for ease of development 8 | - **Advanced Examples**: Use Pinocchio Framework (**~190 total CU**) for maximum optimization 9 | 10 | ## Examples 11 | 12 | ### `runUpdate.ts` - Pinocchio Framework Oracle Updates 13 | Advanced example using the **Pinocchio Framework** for ultra-low compute unit consumption (**~90 CU for feed crank + ~100 CU framework overhead = ~190 total CU**): 14 | 15 | - **Address Lookup Tables (LUTs)**: Reduces transaction size by ~90% 16 | - **Compute Unit Optimization**: Dynamic compute unit estimation with buffers 17 | - **Performance Monitoring**: Latency tracking and statistics 18 | - **Error Handling**: Comprehensive simulation and retry logic 19 | - **Transaction Optimization**: V0 transactions with proper priority fees 20 | 21 | #### Key Features: 22 | - **Pinocchio Framework**: Ultra-optimized account parsing and instruction dispatch 23 | - **Admin Authorization**: Bypasses expensive validation checks for authorized crankers 24 | - **Zero-Allocation Parsing**: Direct syscall access without framework abstractions 25 | - **Optimized CPI**: Direct system program calls with minimal overhead 26 | - **Separate Initialization**: Modular state and quote account setup 27 | - Performance monitoring and comprehensive error handling 28 | 29 | #### Usage: 30 | ```bash 31 | npm run feeds:advanced --feedId=0xef0d8b6fcd0104e3e75096912fc8e1e432893da4f18faedaacca7e5875da620f 32 | ``` 33 | 34 | #### Compute Unit Comparison: 35 | - **Anchor Framework**: ~2k CU with framework overhead 36 | - **Pinocchio Framework**: **~190 total CU** (~90 CU feed crank + ~100 CU framework overhead) 37 | - **Optimization**: ~90% reduction in compute unit consumption 38 | 39 | ### Pinocchio Framework Optimizations: 40 | 41 | 1. **Ultra-Low Compute Units** 42 | - `#[inline(always)]` instruction dispatch 43 | - Direct syscall access without abstractions 44 | - Zero-allocation account parsing with `unsafe` operations 45 | - Admin authorization bypassing expensive validation 46 | 47 | 2. **Modular Account Management** 48 | - Separate state account initialization (`init_state`) 49 | - Separate quote account initialization (`init_oracle`) 50 | - Conditional initialization only when needed 51 | - Proper PDA derivation and validation 52 | 53 | 3. **Framework-Specific Features** 54 | - Uses `pinocchio` crate for minimal runtime overhead 55 | - Direct system program CPI with `invoke_signed` 56 | - Manual account data handling for maximum control 57 | - Custom error codes for precise debugging 58 | 59 | 4. **Performance Monitoring** 60 | - Real-time compute unit tracking 61 | - Latency measurement and statistics 62 | - Comprehensive transaction logging 63 | - Account state debugging -------------------------------------------------------------------------------- /evm/price-feeds/src/switchboard/libraries/SwitchboardTypes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.22; 3 | 4 | /** 5 | * @title SwitchboardTypes 6 | * @notice Library containing types used across the Switchboard oracle system 7 | */ 8 | library SwitchboardTypes { 9 | /** 10 | * @notice Structure containing feed update data 11 | * @param slotNumber The Solana slot number (8 bytes) 12 | * @param timestamp The timestamp of the update 13 | * @param feedInfos Array of feed information in this update 14 | * @param signatures Array of oracle signatures (65 bytes each: r + s + v) 15 | */ 16 | struct FeedUpdateData { 17 | uint64 slotNumber; 18 | uint64 timestamp; 19 | FeedInfo[] feedInfos; 20 | bytes[] signatures; // Array of 65-byte ECDSA signatures (r + s + v) 21 | } 22 | 23 | /** 24 | * @notice Structure containing feed information 25 | * @param feedId The unique identifier (checksum) of the feed 26 | * @param value The current value of the feed 27 | * @param minOracleSamples Minimum number of oracle samples required for this feed 28 | */ 29 | struct FeedInfo { 30 | bytes32 feedId; 31 | int128 value; 32 | uint8 minOracleSamples; 33 | } 34 | 35 | /** 36 | * @notice Packed size constants for data layout compatibility 37 | */ 38 | uint256 constant FEED_INFO_PACKED_SIZE = 49; // 32 + 16 + 1 bytes 39 | 40 | /** 41 | * An update to a feed 42 | * @param result The result of the update 43 | * @param timestamp The timestamp of the update 44 | * @param slotNumber The Solana slot number when the update occurred 45 | */ 46 | struct Update { 47 | int128 result; 48 | uint256 timestamp; 49 | uint64 slotNumber; 50 | } 51 | 52 | /** 53 | * Legacy ABI-compatible update structure (matches old on_demand interface) 54 | * @param feedId The feed identifier (replaces oracleId from legacy) 55 | * @param result The result of the update 56 | * @param timestamp The timestamp of the update 57 | * @param slotNumber The Solana slot number when the update occurred 58 | */ 59 | struct LegacyUpdate { 60 | bytes32 feedId; 61 | int128 result; 62 | uint256 timestamp; 63 | uint64 slotNumber; 64 | } 65 | 66 | /** 67 | * The current result for a feed (compatible with ISwitchboardModule) 68 | * @param result The result of the feed 69 | * @param minTimestamp The minimum timestamp of the feed 70 | * @param maxTimestamp The maximum timestamp of the feed 71 | * @param minResult The minimum result of the feed 72 | * @param maxResult The maximum result of the feed 73 | * @param stdev The standard deviation of the feed 74 | * @param range The range of the feed 75 | * @param mean The mean of the feed 76 | */ 77 | struct CurrentResult { 78 | int128 result; 79 | uint256 minTimestamp; 80 | uint256 maxTimestamp; 81 | int128 minResult; 82 | int128 maxResult; 83 | int128 stdev; 84 | int128 range; 85 | int128 mean; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /evm/price-feeds/src/switchboard/interfaces/ISwitchboard.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.22; 3 | 4 | import { SwitchboardTypes } from '../libraries/SwitchboardTypes.sol'; 5 | 6 | /** 7 | * @title ISwitchboard 8 | * @notice Interface for the Switchboard contract 9 | */ 10 | interface ISwitchboard { 11 | /** 12 | * Update feeds with new oracle results 13 | * @dev This is for backwards compatibility with Switchboard on-demand contracts 14 | * @dev reverts if the timestamp is out of valid range (optional flow for timestamp-sequenced updates) 15 | * @param updates Encoded switchboard update(s) with signatures 16 | */ 17 | function updateFeeds(bytes[] calldata updates) external payable; 18 | 19 | /** 20 | * @notice Main API - Updates feeds from encoded bytes data 21 | * @param feeds The encoded feed update data as bytes 22 | * @return updateData The parsed FeedUpdateData struct 23 | */ 24 | function updateFeeds( 25 | bytes calldata feeds 26 | ) 27 | external 28 | payable 29 | returns (SwitchboardTypes.FeedUpdateData memory updateData); 30 | 31 | /** 32 | * @notice Gets a verified feed value or reverts 33 | * @param updateData The feed update data 34 | * @param feedId The ID of the feed 35 | * @return value The verified value of the feed 36 | * @return timestamp The verified timestamp of the feed 37 | */ 38 | function getFeedValue( 39 | SwitchboardTypes.FeedUpdateData calldata updateData, 40 | bytes32 feedId 41 | ) 42 | external 43 | view 44 | returns (int256 value, uint256 timestamp, uint64 slotNumber); 45 | 46 | /** 47 | * @notice Gets the verifier contract address 48 | * @return The address of the verifier contract 49 | */ 50 | function verifierAddress() external view returns (address); 51 | 52 | /** 53 | * @notice Gets the implementation address 54 | * @return The address of the implementation contract 55 | */ 56 | function implementation() external view returns (address); 57 | 58 | /** 59 | * Get the latest Update struct for a feed 60 | * @dev This is for backwards compatibility with the old Switchboard contracts 61 | * @dev Intended to be called within the same transaction as a feed update for the most up-to-date data. 62 | * @dev Reverts if the feed does not have the minimum number of valid responses 63 | * @param feedId The identifier for the feed to get the latest update for 64 | * @return Update The latest update for the given feed (LegacyUpdate format for ABI compatibility) 65 | */ 66 | function latestUpdate( 67 | bytes32 feedId 68 | ) external view returns (SwitchboardTypes.LegacyUpdate memory); 69 | 70 | /** 71 | * Get the fee in wei for submitting a set of updates 72 | * @dev This is for backwards compatibility with the old Switchboard contracts 73 | * @param updates Encoded switchboard update(s) with signatures 74 | * @return uint256 The fee in wei for submitting the updates 75 | */ 76 | function getFee(bytes[] calldata updates) external view returns (uint256); 77 | } 78 | -------------------------------------------------------------------------------- /evm/legacy/examples/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Utility functions for EVM Switchboard On-Demand examples 3 | */ 4 | 5 | import * as ethers from "ethers"; 6 | 7 | /** 8 | * Default transaction configuration for EVM networks 9 | */ 10 | export const TX_CONFIG = { 11 | // Max priority fee per gas (in gwei) 12 | maxPriorityFeePerGas: ethers.parseUnits("2", "gwei"), 13 | // Max fee per gas (in gwei) 14 | maxFeePerGas: ethers.parseUnits("50", "gwei"), 15 | }; 16 | 17 | /** 18 | * Sleep utility function 19 | * @param {number} ms - Milliseconds to sleep 20 | * @returns {Promise} 21 | */ 22 | export const sleep = (ms: number): Promise => 23 | new Promise((resolve) => setTimeout(resolve, ms)); 24 | 25 | /** 26 | * Format a price value for display 27 | * @param {bigint} value - The raw price value from the contract 28 | * @param {number} decimals - Number of decimals (default 18) 29 | * @returns {string} Formatted price string 30 | */ 31 | export const formatPrice = (value: bigint, decimals: number = 18): string => { 32 | const formatted = ethers.formatUnits(value, decimals); 33 | return parseFloat(formatted).toFixed(2); 34 | }; 35 | 36 | /** 37 | * Retry a function with exponential backoff 38 | * @param {Function} fn - Function to retry 39 | * @param {number} maxRetries - Maximum number of retries 40 | * @param {number} baseDelay - Base delay in milliseconds 41 | * @returns {Promise} Result of the function 42 | */ 43 | export const retryWithBackoff = async ( 44 | fn: () => Promise, 45 | maxRetries: number = 3, 46 | baseDelay: number = 1000 47 | ): Promise => { 48 | let lastError; 49 | 50 | for (let i = 0; i < maxRetries; i++) { 51 | try { 52 | return await fn(); 53 | } catch (error) { 54 | lastError = error; 55 | if (i < maxRetries - 1) { 56 | const delay = baseDelay * Math.pow(2, i); 57 | console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms delay...`); 58 | await sleep(delay); 59 | } 60 | } 61 | } 62 | 63 | throw lastError; 64 | }; 65 | 66 | /** 67 | * Common Switchboard contract addresses by chain ID 68 | */ 69 | export const SWITCHBOARD_ADDRESSES: Record = { 70 | 1: "0x...", // Ethereum Mainnet (placeholder) 71 | 42161: "0x...", // Arbitrum One (placeholder) 72 | 999: "0x316fbe540c719970e6427ccd8590d7e0a2814c5d", // Hyperliquid 73 | 10: "0x...", // Optimism (placeholder) 74 | 137: "0x...", // Polygon (placeholder) 75 | 8453: "0x...", // Base (placeholder) 76 | 56: "0x...", // BNB Chain (placeholder) 77 | 43114: "0x...", // Avalanche (placeholder) 78 | }; 79 | 80 | /** 81 | * Get Switchboard contract address for a given chain ID 82 | * @param {number} chainId - The chain ID 83 | * @returns {string} The Switchboard contract address 84 | * @throws {Error} If chain ID is not supported 85 | */ 86 | export const getSwitchboardAddress = (chainId: number): string => { 87 | const address = SWITCHBOARD_ADDRESSES[chainId]; 88 | if (!address) { 89 | throw new Error(`Unsupported chain ID: ${chainId}`); 90 | } 91 | return address; 92 | }; 93 | 94 | /** 95 | * Example aggregator IDs for different price feeds 96 | */ 97 | export const EXAMPLE_FEEDS = { 98 | // Hyperliquid feeds 99 | "ETH/USD": "0x...", // placeholder 100 | "BTC/USD": "0x...", // placeholder 101 | "UNI/USD": "0x755c0da00f939b04266f3ba3619ad6498fb936a8bfbfac27c9ecd4ab4c5d4878", 102 | "CARBON_INTENSITY_GB": "0xba2c99cb1c50d8c77209adc5a45f82e561c29f5b279dca507b4f1324b6586572", 103 | }; -------------------------------------------------------------------------------- /evm/legacy/src/Example.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import {ISwitchboard} from "@switchboard-xyz/on-demand-solidity/ISwitchboard.sol"; 5 | import {Structs} from "@switchboard-xyz/on-demand-solidity/structs/Structs.sol"; 6 | 7 | contract Example { 8 | ISwitchboard switchboard; 9 | 10 | // Every Switchboard Feed has a unique aggregatorId. 11 | bytes32 public aggregatorId; 12 | 13 | // The latest price from the feed 14 | int256 public latestPrice; 15 | 16 | // Latest update timestamp 17 | uint256 public lastUpdateTimestamp; 18 | 19 | // Latest oracle ID that provided the update 20 | bytes32 public lastOracleId; 21 | 22 | // If the transaction fee is not paid, the update will fail. 23 | error InsufficientFee(uint256 expected, uint256 received); 24 | 25 | // If the feed result is invalid, this error will be emitted. 26 | error InvalidResult(int128 result); 27 | 28 | // If the Switchboard update succeeds, this event will be emitted with the latest price. 29 | event FeedData(int128 price, uint256 timestamp, bytes32 oracleId); 30 | 31 | /** 32 | * @param _switchboard The address of the Switchboard contract 33 | * @param _aggregatorId The feed ID for the feed you want to query 34 | */ 35 | constructor(address _switchboard, bytes32 _aggregatorId) { 36 | // Initialize the target _switchboard 37 | // Get the existing Switchboard contract address on your preferred network from the Switchboard Docs 38 | switchboard = ISwitchboard(_switchboard); 39 | aggregatorId = _aggregatorId; 40 | } 41 | 42 | /** 43 | * getFeedData is a function that uses an encoded Switchboard update 44 | * If the update is successful, it will read the latest price from the feed 45 | * See below for fetching encoded updates (e.g., using the Switchboard Typescript SDK) 46 | * @param updates Encoded feed updates to update the contract with the latest result 47 | */ 48 | function getFeedData(bytes[] calldata updates) public payable { 49 | // Get the fee for updating the feeds. If the transaction fee is not paid, the update will fail. 50 | uint256 fee = switchboard.getFee(updates); 51 | if (msg.value < fee) { 52 | revert InsufficientFee(fee, msg.value); 53 | } 54 | 55 | // Submit the updates to the Switchboard contract 56 | switchboard.updateFeeds{value: fee}(updates); 57 | 58 | // Read the current value from a Switchboard feed. 59 | // This will fail if the feed doesn't have fresh updates ready (e.g. if the feed update failed) 60 | // Get the latest feed result - this returns a complete Update struct 61 | Structs.Update memory update = switchboard.latestUpdate(aggregatorId); 62 | 63 | // Store the complete update information 64 | latestPrice = update.result; 65 | lastUpdateTimestamp = update.timestamp; 66 | lastOracleId = update.oracleId; 67 | 68 | // Emit the latest result from the feed with full update info 69 | emit FeedData(update.result, update.timestamp, update.oracleId); 70 | } 71 | 72 | /** 73 | * Get the latest update information 74 | * @return result The latest price result 75 | * @return timestamp The timestamp of the update 76 | * @return oracleId The oracle that provided the update 77 | */ 78 | function getLatestUpdate() external view returns (int128 result, uint256 timestamp, bytes32 oracleId) { 79 | return (int128(latestPrice), lastUpdateTimestamp, lastOracleId); 80 | } 81 | } -------------------------------------------------------------------------------- /evm/price-feeds/scripts/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Utility functions for EVM Switchboard On-Demand examples 3 | */ 4 | 5 | import * as ethers from "ethers"; 6 | 7 | /** 8 | * Default transaction configuration for EVM networks 9 | */ 10 | export const TX_CONFIG = { 11 | // Max priority fee per gas (in gwei) 12 | maxPriorityFeePerGas: ethers.parseUnits("2", "gwei"), 13 | // Max fee per gas (in gwei) 14 | maxFeePerGas: ethers.parseUnits("50", "gwei"), 15 | }; 16 | 17 | /** 18 | * Sleep utility function 19 | * @param {number} ms - Milliseconds to sleep 20 | * @returns {Promise} 21 | */ 22 | export const sleep = (ms: number): Promise => 23 | new Promise((resolve) => setTimeout(resolve, ms)); 24 | 25 | /** 26 | * Format a price value for display 27 | * @param {bigint} value - The raw price value from the contract 28 | * @param {number} decimals - Number of decimals (default 18) 29 | * @returns {string} Formatted price string 30 | */ 31 | export const formatPrice = (value: bigint, decimals: number = 18): string => { 32 | const formatted = ethers.formatUnits(value, decimals); 33 | return parseFloat(formatted).toFixed(2); 34 | }; 35 | 36 | /** 37 | * Retry a function with exponential backoff 38 | * @param {Function} fn - Function to retry 39 | * @param {number} maxRetries - Maximum number of retries 40 | * @param {number} baseDelay - Base delay in milliseconds 41 | * @returns {Promise} Result of the function 42 | */ 43 | export const retryWithBackoff = async ( 44 | fn: () => Promise, 45 | maxRetries: number = 3, 46 | baseDelay: number = 1000 47 | ): Promise => { 48 | let lastError; 49 | 50 | for (let i = 0; i < maxRetries; i++) { 51 | try { 52 | return await fn(); 53 | } catch (error) { 54 | lastError = error; 55 | if (i < maxRetries - 1) { 56 | const delay = baseDelay * Math.pow(2, i); 57 | console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms delay...`); 58 | await sleep(delay); 59 | } 60 | } 61 | } 62 | 63 | throw lastError; 64 | }; 65 | 66 | /** 67 | * Common Switchboard contract addresses by chain ID 68 | */ 69 | export const SWITCHBOARD_ADDRESSES: Record = { 70 | 1: "0x...", // Ethereum Mainnet (placeholder) 71 | 42161: "0x...", // Arbitrum One (placeholder) 72 | 999: "0x316fbe540c719970e6427ccd8590d7e0a2814c5d", // Hyperliquid 73 | 10: "0x...", // Optimism (placeholder) 74 | 137: "0x...", // Polygon (placeholder) 75 | 8453: "0x...", // Base (placeholder) 76 | 56: "0x...", // BNB Chain (placeholder) 77 | 43114: "0x...", // Avalanche (placeholder) 78 | }; 79 | 80 | /** 81 | * Get Switchboard contract address for a given chain ID 82 | * @param {number} chainId - The chain ID 83 | * @returns {string} The Switchboard contract address 84 | * @throws {Error} If chain ID is not supported 85 | */ 86 | export const getSwitchboardAddress = (chainId: number): string => { 87 | const address = SWITCHBOARD_ADDRESSES[chainId]; 88 | if (!address) { 89 | throw new Error(`Unsupported chain ID: ${chainId}`); 90 | } 91 | return address; 92 | }; 93 | 94 | /** 95 | * Example aggregator IDs for different price feeds 96 | */ 97 | export const EXAMPLE_FEEDS = { 98 | // Hyperliquid feeds 99 | "ETH/USD": "0x...", // placeholder 100 | "BTC/USD": "0x...", // placeholder 101 | "UNI/USD": "0x755c0da00f939b04266f3ba3619ad6498fb936a8bfbfac27c9ecd4ab4c5d4878", 102 | "CARBON_INTENSITY_GB": "0xba2c99cb1c50d8c77209adc5a45f82e561c29f5b279dca507b4f1324b6586572", 103 | }; -------------------------------------------------------------------------------- /common/variable-overrides/testVariableOverrides.ts: -------------------------------------------------------------------------------- 1 | import { CrossbarClient, OracleJob } from "@switchboard-xyz/common"; 2 | import dotenv from "dotenv"; 3 | 4 | /** 5 | * Chain-agnostic example demonstrating variable overrides with Crossbar simulation 6 | * 7 | * Run with: POLYGON_API_KEY=your_key bun run testVariableOverrides.ts 8 | * 9 | * Get your API key from: https://polygon.io/ 10 | */ 11 | 12 | /** 13 | * Builds an Oracle job using variable overrides for API authentication. 14 | * 15 | * Following security best practices: 16 | * - ✅ Only use variables for API keys/authentication 17 | * - ✅ Hardcode all data sources, paths, and parameters 18 | * - ✅ Ensure feed verifiability by making data extraction deterministic 19 | */ 20 | function buildPolygonAuthJob(): OracleJob { 21 | const jobConfig = { 22 | tasks: [ 23 | { 24 | httpTask: { 25 | // ✅ Hardcoded endpoint - verifiable data source 26 | url: "https://api.polygon.io/v2/last/trade/AAPL", 27 | method: "GET", 28 | headers: [ 29 | { 30 | key: "Authorization", 31 | value: "Bearer ${POLYGON_API_KEY}", 32 | }, 33 | ], 34 | }, 35 | }, 36 | { 37 | jsonParseTask: { 38 | // ✅ Hardcoded path - verifiable data extraction 39 | path: "$.results[0].p", 40 | }, 41 | }, 42 | ], 43 | }; 44 | return OracleJob.fromObject(jobConfig); 45 | } 46 | 47 | async function main() { 48 | try { 49 | dotenv.config(); 50 | 51 | // Check for required API key 52 | if (!process.env.POLYGON_API_KEY) { 53 | console.error("❌ Error: POLYGON_API_KEY environment variable is required"); 54 | console.error("\nGet your API key from: https://polygon.io/"); 55 | console.error("Then run: POLYGON_API_KEY=your_key bun run testVariableOverrides.ts"); 56 | process.exit(1); 57 | } 58 | 59 | const crossbarUrl = process.env.CROSSBAR_URL || "https://crossbar.switchboard.xyz"; 60 | const crossbarClient = new CrossbarClient(crossbarUrl); 61 | 62 | console.log("🧪 Variable Overrides Example\n"); 63 | console.log("📊 Fetching AAPL stock price from Polygon.io"); 64 | console.log("🔐 Using variable override for API authentication\n"); 65 | 66 | // Build oracle job with API key placeholder 67 | const job = buildPolygonAuthJob(); 68 | 69 | console.log("📋 Job Definition:"); 70 | console.log(JSON.stringify(job.toJSON(), null, 2)); 71 | console.log("\n🔑 Variable Override: POLYGON_API_KEY = *** (hidden)"); 72 | console.log("🎯 Target: AAPL (hardcoded symbol)\n"); 73 | 74 | // Simulate feed with Crossbar 75 | console.log("⚡ Simulating feed with Crossbar..."); 76 | const result = await crossbarClient.simulateFeed( 77 | [job], 78 | { POLYGON_API_KEY: process.env.POLYGON_API_KEY } 79 | ); 80 | 81 | console.log("✅ Success!\n"); 82 | console.log("💰 AAPL Price:", result.results?.[0]); 83 | 84 | console.log("\n🔑 Key Takeaways:"); 85 | console.log(" ✅ Variable used ONLY for API key (authorization header)"); 86 | console.log(" ✅ Data source and symbol are hardcoded (verifiable)"); 87 | console.log(" ✅ JSON path is hardcoded (verifiable extraction)"); 88 | console.log(" ✅ Works identically on Solana, EVM, and Sui"); 89 | 90 | } catch (error) { 91 | console.error("\n❌ Error:", error); 92 | if (error instanceof Error) { 93 | console.error("Message:", error.message); 94 | } 95 | process.exit(1); 96 | } 97 | } 98 | 99 | main(); 100 | -------------------------------------------------------------------------------- /solana/surge/README.md: -------------------------------------------------------------------------------- 1 | # Surge Streaming - Real-time Signed Price Updates 2 | 3 | Real-time WebSocket streaming of signed oracle price data with detailed latency metrics. 4 | 5 | ## Quick Start 6 | 7 | ```bash 8 | # Default: stream BTC/USD prices 9 | npx ts-node surge/runSurge.ts 10 | 11 | # Custom ticker 12 | npx ts-node surge/runSurge.ts -t ETH 13 | 14 | # With program simulation (requires deployed basic_oracle_example) 15 | npx ts-node surge/runSurge.ts -p 16 | ``` 17 | 18 | ## CLI Options 19 | 20 | | Flag | Alias | Description | Default | 21 | |------|-------|-------------|---------| 22 | | `--ticker` | `-t` | Trading pair symbol (USD quote assumed) | `BTC` | 23 | | `--withProgram` | `-p` | Include program read instruction in simulation | `false` | 24 | 25 | ## Authentication 26 | 27 | Two authentication modes are supported. Configure in the `surgeConfig` object: 28 | 29 | ```typescript 30 | // Option 1: Keypair/connection (default, on-chain subscription) 31 | const surgeConfig = { 32 | connection, 33 | keypair, 34 | verbose: false, 35 | }; 36 | 37 | // Option 2: API key 38 | const surgeConfig = { 39 | apiKey: process.env.SURGE_API_KEY, 40 | verbose: false, 41 | }; 42 | ``` 43 | 44 | ## Output Example 45 | 46 | ``` 47 | 🚀 Starting Surge streaming demo... 48 | 📊 Using ticker: BTC/USD 49 | 🔑 Loaded keypair: 2PnGjGspy5Hbe... 50 | 🌐 Connected to cluster: https://api.mainnet-beta.solana.com 51 | 📡 Listening for price updates (will simulate after 10 seconds)... 52 | 53 | ══════════════════════════════════════════════════════════════════════ 54 | 📈 PRICE CHANGE | Update #1 55 | ────────────────────────────────────────────────────────────────────── 56 | Bundle Metrics: 57 | • Emit Latency: 145ms (price source change → oracle broadcast) 58 | • Change Detection to Bcast: 11ms (price change detection → broadcast) 59 | • Oracle → Client: 12ms (network latency to your client) 60 | ────────────────────────────────────────────────────────────────────── 61 | 62 | BTC/USD - $89,868.62 63 | • Source → Oracle: 134ms (exchange to oracle reception) 64 | • Emit Latency: 145ms (price source change → oracle broadcast) 65 | 66 | ══════════════════════════════════════════════════════════════════════ 67 | 68 | ══════════════════════════════════════════════════════════════════════ 69 | ⏰ HEARTBEAT | Update #2 70 | ────────────────────────────────────────────────────────────────────── 71 | ... 72 | ``` 73 | 74 | ## Event Handling 75 | 76 | ```typescript 77 | surge.on('signedPriceUpdate', (response: sb.SurgeUpdate) => { 78 | const prices = response.getFormattedPrices(); // { "BTC/USD": "$89,868.62" } 79 | const metrics = response.getLatencyMetrics(); // Detailed timing breakdown 80 | 81 | // Check update type 82 | if (metrics.isHeartbeat) { 83 | // No price change, just keeping connection alive 84 | } else { 85 | // Actual price change 86 | } 87 | 88 | // Per-feed metrics 89 | metrics.perFeedMetrics.forEach((feed) => { 90 | console.log(feed.symbol, feed.emitLatencyMs); 91 | }); 92 | }); 93 | ``` 94 | 95 | ## Simulation Modes 96 | 97 | After 10 seconds of streaming, the script runs a transaction simulation: 98 | 99 | - **Default**: Simulates oracle quote update only 100 | - **With `-p` flag**: Simulates oracle update + reading from your program 101 | 102 | Use `-p` to test the full flow of updating oracle data and consuming it in your program. Requires the `basic_oracle_example` program to be deployed. 103 | 104 | ## Related 105 | 106 | - [`../../common/streaming/`](../../common/streaming/) - Chain-agnostic streaming (unsigned data) 107 | -------------------------------------------------------------------------------- /solana/feeds/basic/programs/basic-oracle-example/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use switchboard_on_demand::{ 3 | SlotHashes, Instructions, default_queue, SwitchboardQuoteExt, SwitchboardQuote 4 | }; 5 | 6 | declare_id!("9kVBXoCrvZgKYWTJ74w3S8wAp7daEB7zpG7kwiXxkCVN"); 7 | 8 | /// Basic Oracle Example Program 9 | /// 10 | /// This program demonstrates the simplest possible integration with 11 | /// Switchboard's managed update system. Perfect for learning and 12 | /// simple applications. 13 | #[program] 14 | pub mod basic_oracle_example { 15 | use super::*; 16 | 17 | /// Read and verify oracle data from the managed oracle account 18 | /// 19 | /// This is the simplest way to consume Switchboard oracle data. 20 | /// The oracle account is derived canonically from feed hashes and 21 | /// updated by the quote program's verified_update instruction. 22 | /// 23 | /// ## Usage 24 | /// 1. Call fetchManagedUpdateIxs to update the oracle account 25 | /// 2. Call this instruction to read the verified data 26 | /// 27 | /// ## Parameters 28 | /// - quote_account: The canonical oracle account (derived from feed hashes) 29 | /// - queue: The Switchboard queue (auto-detected by network) 30 | /// - sysvars: Required system variables for verification 31 | pub fn read_oracle_data(ctx: Context) -> Result<()> { 32 | // Access the oracle data directly 33 | // The quote_account constraint validates it's the canonical account 34 | let feeds = &ctx.accounts.quote_account.feeds; 35 | 36 | // Calculate staleness 37 | let current_slot = ctx.accounts.sysvars.clock.slot; 38 | let quote_slot = ctx.accounts.quote_account.slot; 39 | let staleness = current_slot.saturating_sub(quote_slot); 40 | 41 | msg!("Number of feeds: {}", feeds.len()); 42 | msg!("📅 Quote slot: {}, Current slot: {}", quote_slot, current_slot); 43 | msg!("⏰ Staleness: {} slots", staleness); 44 | 45 | // Process each feed 46 | for (i, feed) in feeds.iter().enumerate() { 47 | msg!("📊 Feed {}: ID = {}", i, feed.hex_id()); 48 | msg!("💰 Feed {}: Value = {}", i, feed.value()); 49 | 50 | // Your business logic here! 51 | // For example: 52 | // - Store the price in your program state 53 | // - Trigger events based on price changes 54 | // - Use the price for calculations 55 | } 56 | 57 | msg!("✅ Successfully read {} oracle feeds!", feeds.len()); 58 | Ok(()) 59 | } 60 | } 61 | 62 | /// Account context for reading oracle data 63 | /// 64 | /// This is designed to be as simple as possible while still being secure. 65 | /// The quote_account is the canonical account derived from feed hashes. 66 | #[derive(Accounts)] 67 | pub struct ReadOracleData<'info> { 68 | /// The canonical oracle account containing verified quote data 69 | /// 70 | /// This account is: 71 | /// - Updated by the quote program's verified_update instruction 72 | /// - Contains verified oracle data 73 | /// - Validated to be the canonical account for the contained feeds 74 | #[account(address = quote_account.canonical_key(&default_queue()))] 75 | pub quote_account: Box>, 76 | 77 | /// System variables required for quote verification 78 | pub sysvars: Sysvars<'info>, 79 | } 80 | 81 | /// System variables required for oracle verification 82 | #[derive(Accounts)] 83 | pub struct Sysvars<'info> { 84 | pub clock: Sysvar<'info, Clock>, 85 | pub slothashes: Sysvar<'info, SlotHashes>, 86 | pub instructions: Sysvar<'info, Instructions>, 87 | } 88 | -------------------------------------------------------------------------------- /evm/randomness/coin-flip/scripts/flip-coin.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { CrossbarClient } from "@switchboard-xyz/common"; 3 | 4 | async function main() { 5 | 6 | const privateKey = process.env.PRIVATE_KEY; 7 | if (!privateKey) { 8 | throw new Error("PRIVATE_KEY is not set"); 9 | } 10 | 11 | const provider = new ethers.JsonRpcProvider("https://rpc.monad.xyz"); 12 | const wallet = new ethers.Wallet(privateKey, provider); 13 | 14 | // Initialize Crossbar 15 | const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz"); 16 | 17 | const COIN_FLIP_CONTRACT_ADDRESS = process.env.COIN_FLIP_CONTRACT_ADDRESS; 18 | 19 | if (!COIN_FLIP_CONTRACT_ADDRESS) { 20 | throw new Error("COIN_FLIP_CONTRACT_ADDRESS is not set"); 21 | } 22 | 23 | // CoinFlip ABI 24 | const coinFlipAbi = [ 25 | "function getWagerRandomnessId(address user) public view returns (bytes32)", 26 | "function coinFlip() public payable", 27 | "function settleFlip(bytes calldata encodedRandomness) public", 28 | "function getWagerData(address user) public view returns (address oracle, uint256 rollTimestamp, uint256 minSettlementDelay)", 29 | "event CoinFlipped(address indexed user, bytes32 randomnessId, uint256 amount)", 30 | "event FlipSettled(address indexed user, bool won, uint256 payout, uint256 randomValue)", 31 | ]; 32 | 33 | // CoinFlip contract 34 | const coinFlipContract = new ethers.Contract(COIN_FLIP_CONTRACT_ADDRESS, coinFlipAbi, wallet); 35 | 36 | // Run the coin flip 37 | const tx = await coinFlipContract.coinFlip({ value: ethers.parseEther("1") }); 38 | await tx.wait(); 39 | console.log("Coin flip transaction sent", tx); 40 | 41 | // Get the wager randomness ID 42 | const wagerRandomnessId: string = await coinFlipContract.getWagerRandomnessId(wallet.address); 43 | console.log("Wager randomness ID:", wagerRandomnessId); 44 | 45 | 46 | const wagerData = await coinFlipContract.getWagerData(wallet.address); 47 | console.log("Wager data:", wagerData); 48 | 49 | // Get the randomness from Switchboard 50 | const { encoded } = await crossbar.resolveEVMRandomness({ 51 | chainId: 143, 52 | randomnessId: wagerRandomnessId, 53 | timestamp: Number(wagerData.rollTimestamp), 54 | minStalenessSeconds: Number(wagerData.minSettlementDelay), 55 | oracle: wagerData.oracle, 56 | }); 57 | 58 | console.log("Encoded randomness:", encoded); 59 | 60 | // Settle the flip 61 | const tx2 = await coinFlipContract.settleFlip(encoded); 62 | const receipt = await tx2.wait(); 63 | 64 | // Parse the FlipSettled event to get the result 65 | const settledEvent = receipt.logs 66 | .map((log: any) => { 67 | try { 68 | return coinFlipContract.interface.parseLog(log); 69 | } catch { 70 | return null; 71 | } 72 | }) 73 | .find((event: any) => event?.name === "FlipSettled"); 74 | 75 | if (settledEvent) { 76 | const { won, payout, randomValue } = settledEvent.args; 77 | 78 | console.log("\n========================================"); 79 | if (won) { 80 | console.log("🎉 YOU WON!"); 81 | console.log(`💰 Payout: ${ethers.formatEther(payout)} ETH`); 82 | } else { 83 | console.log("😔 YOU LOST"); 84 | console.log("💸 Better luck next time!"); 85 | } 86 | console.log(`🎲 Random value: ${randomValue}`); 87 | console.log(`📝 Transaction: ${tx2.hash}`); 88 | console.log("========================================\n"); 89 | } else { 90 | console.log("Flip settled:", tx2.hash); 91 | } 92 | } 93 | 94 | main(); -------------------------------------------------------------------------------- /solana/legacy/feeds/runFeed.ts: -------------------------------------------------------------------------------- 1 | import * as sb from "@switchboard-xyz/on-demand"; 2 | import { CrossbarClient } from "@switchboard-xyz/common"; 3 | import yargs from "yargs"; 4 | import { TX_CONFIG, sleep } from "../../utils"; 5 | import { DisplayState, render, initScreen, setupCleanupHandlers } from "./view"; 6 | 7 | const argv = yargs(process.argv).options({ feed: { required: true } }) 8 | .argv as any; 9 | 10 | function calculateStatistics(latencies: number[]) { 11 | const sortedLatencies = [...latencies].sort((a, b) => a - b); 12 | const min = sortedLatencies[0]; 13 | const max = sortedLatencies[sortedLatencies.length - 1]; 14 | const median = 15 | sortedLatencies.length % 2 === 0 16 | ? (sortedLatencies[sortedLatencies.length / 2 - 1] + 17 | sortedLatencies[sortedLatencies.length / 2]) / 18 | 2 19 | : sortedLatencies[Math.floor(sortedLatencies.length / 2)]; 20 | const sum = sortedLatencies.reduce((a, b) => a + b, 0); 21 | const mean = sum / sortedLatencies.length; 22 | 23 | return { 24 | min, 25 | max, 26 | median, 27 | mean, 28 | count: latencies.length, 29 | }; 30 | } 31 | 32 | (async function main() { 33 | // Setup screen and cleanup handlers 34 | setupCleanupHandlers(); 35 | initScreen(); 36 | 37 | const { keypair, connection, program } = await sb.AnchorUtils.loadEnv(); 38 | const queue = await sb.Queue.loadDefault(program!); 39 | const feedAccount = new sb.PullFeed(program!, argv.feed!); 40 | const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz"); 41 | await feedAccount.preHeatLuts(); 42 | const latencies: number[] = []; 43 | 44 | const state: DisplayState = { 45 | feedKey: argv.feed!, 46 | feedValue: null, 47 | slot: null, 48 | error: null, 49 | logs: [], 50 | stats: null, 51 | lastUpdate: new Date(), 52 | status: "fetching", 53 | }; 54 | 55 | // Initial render 56 | render(state); 57 | 58 | while (true) { 59 | state.status = "fetching"; 60 | state.lastUpdate = new Date(); 61 | render(state); 62 | 63 | const start = Date.now(); 64 | const [pullIx, responses, _ok, luts] = await feedAccount.fetchUpdateIx({ 65 | crossbarClient: crossbar, 66 | }); 67 | const endTime = Date.now(); 68 | 69 | // Check for oracle errors 70 | for (const response of responses) { 71 | const shortErr = response.shortError(); 72 | if (shortErr) { 73 | state.error = shortErr; 74 | state.status = "error"; 75 | render(state); 76 | } 77 | } 78 | 79 | state.status = "simulating"; 80 | render(state); 81 | 82 | const tx = await sb.asV0Tx({ 83 | connection, 84 | ixs: [...pullIx!], 85 | signers: [keypair], 86 | computeUnitPrice: 200_000, 87 | computeUnitLimitMultiple: 1.3, 88 | lookupTables: luts, 89 | }); 90 | 91 | const sim = await connection.simulateTransaction(tx, TX_CONFIG); 92 | 93 | if (sim.value.err) { 94 | state.error = JSON.stringify(sim.value.err); 95 | state.logs = sim.value.logs || []; 96 | state.status = "error"; 97 | } else { 98 | state.error = null; 99 | state.feedValue = responses[0]?.value?.toString() || "N/A"; 100 | state.slot = sim.context.slot; 101 | state.logs = (sim.value.logs || []).filter( 102 | (l) => l.includes("Program log:") || l.includes("Program data:") 103 | ); 104 | state.status = "success"; 105 | } 106 | 107 | const latency = endTime - start; 108 | latencies.push(latency); 109 | state.stats = calculateStatistics(latencies); 110 | state.lastUpdate = new Date(); 111 | 112 | render(state); 113 | 114 | await sleep(3000); 115 | } 116 | })(); 117 | -------------------------------------------------------------------------------- /evm/legacy/README.md: -------------------------------------------------------------------------------- 1 | # Legacy EVM Examples 2 | 3 | This directory contains the previous version of the EVM examples using the older Switchboard implementation. 4 | 5 | ## 📁 Contents 6 | 7 | - **`src/Example.sol`** - Basic example contract using the legacy Switchboard interface 8 | - **`script/Deploy.s.sol`** - Deployment script for the legacy example 9 | - **`examples/`** - TypeScript client examples for the legacy implementation 10 | 11 | ## 🌐 Legacy Network Support 12 | 13 | These examples work with the following networks using the previous Switchboard contracts: 14 | 15 | | Network | Chain ID | Switchboard Contract | 16 | |---------|----------|---------------------| 17 | | Arbitrum One | 42161 | `0xAd9b8604b6B97187CDe9E826cDeB7033C8C37198` | 18 | | Arbitrum Sepolia | 421614 | `0xA2a0425fA3C5669d384f4e6c8068dfCf64485b3b` | 19 | | Core Mainnet | 1116 | `0x33A5066f65f66161bEb3f827A3e40fce7d7A2e6C` | 20 | | Core Testnet | 1114 | `0x33A5066f65f66161bEb3f827A3e40fce7d7A2e6C` | 21 | | HyperEVM Mainnet | 999 | `0x316fbe540c719970e6427ccd8590d7e0a2814c5d` | 22 | 23 | ## 🚀 Quick Start (Legacy) 24 | 25 | ```bash 26 | # Install dependencies 27 | bun install 28 | 29 | # Deploy to Hyperliquid (example) 30 | forge script legacy/script/Deploy.s.sol:DeployScript \ 31 | --rpc-url https://rpc.hyperliquid.xyz/evm \ 32 | --broadcast \ 33 | -vv 34 | 35 | # Run legacy update example 36 | export PRIVATE_KEY=0x... 37 | export EXAMPLE_ADDRESS=0x... 38 | bun run legacy/examples/updateFeed.ts 39 | ``` 40 | 41 | ## 📖 Documentation 42 | 43 | For detailed documentation on the legacy implementation, see the original README structure: 44 | 45 | ### Legacy Example Contract 46 | 47 | ```solidity 48 | contract Example { 49 | ISwitchboard switchboard; 50 | bytes32 public aggregatorId; 51 | int256 public latestPrice; 52 | 53 | function getFeedData(bytes[] calldata updates) public payable { 54 | uint256 fee = switchboard.getFee(updates); 55 | require(msg.value >= fee, "Insufficient fee"); 56 | 57 | switchboard.updateFeeds{value: fee}(updates); 58 | 59 | Structs.Update memory update = switchboard.latestUpdate(aggregatorId); 60 | latestPrice = update.result; 61 | } 62 | } 63 | ``` 64 | 65 | ### Legacy TypeScript Usage 66 | 67 | ```typescript 68 | import * as ethers from "ethers"; 69 | import { CrossbarClient } from "@switchboard-xyz/common"; 70 | 71 | const crossbar = new CrossbarClient(`https://crossbar.switchboard.xyz`); 72 | 73 | // Get the encoded updates 74 | const { encoded } = await crossbar.fetchEVMResults({ 75 | chainId: 999, 76 | aggregatorIds: [aggregatorId], 77 | }); 78 | 79 | // Update the contract 80 | const tx = await exampleContract.getFeedData(encoded); 81 | await tx.wait(); 82 | ``` 83 | 84 | ## ⚠️ Migration to New Implementation 85 | 86 | For new projects, we recommend using the new implementation in the parent directory which includes: 87 | 88 | - **Better Security**: Price deviation validation and staleness checks 89 | - **More Features**: Multi-feed support and business logic helpers 90 | - **Production Ready**: Comprehensive error handling and events 91 | - **Monad Support**: Optimized for Monad Mainnet and Testnet 92 | 93 | See the [main README](../README.md) for the new implementation. 94 | 95 | ## 📚 Resources 96 | 97 | - [Switchboard Documentation](https://docs.switchboard.xyz) 98 | - [Legacy Contract Addresses](https://docs.switchboard.xyz/product-documentation/data-feeds/evm/contract-addresses) 99 | - [Discord Community](https://discord.gg/switchboardxyz) 100 | 101 | --- 102 | 103 | **Note**: This legacy implementation is maintained for reference and compatibility with existing deployments on non-Monad chains. For new projects, please use the updated implementation in the parent directory. 104 | 105 | -------------------------------------------------------------------------------- /common/README.md: -------------------------------------------------------------------------------- 1 | # Common Resources 2 | 3 | This directory contains chain-agnostic resources and tools that work across all Switchboard On-Demand blockchain implementations. 4 | 5 | ## What's Here 6 | 7 | ### Variable Overrides 8 | The `variable-overrides/` directory demonstrates secure credential management for oracle feeds: 9 | - **`testVariableOverrides.ts`** - Chain-agnostic variable override examples 10 | - **Security best practices** - Only use variables for API keys/auth tokens 11 | - **Full documentation** - See [variable-overrides/README.md](./variable-overrides/README.md) 12 | 13 | ### Job Testing Tools 14 | The `job-testing/` directory provides tools for testing and developing custom oracle job definitions: 15 | - **`runJob.ts`** - Test oracle jobs with variable substitution and API integrations 16 | - **Chain-agnostic** - Works with Solana, EVM, and Sui 17 | - **Full documentation** - See [job-testing/README.md](./job-testing/README.md) 18 | 19 | ### Streaming Examples 20 | The `streaming/` directory contains chain-agnostic real-time price streaming examples: 21 | - **`crossbarStream.ts`** - Stream unsigned price updates via WebSocket for UI/monitoring 22 | - **Chain-agnostic** - Works with any blockchain 23 | - **Full documentation** - See [streaming/README.md](./streaming/README.md) 24 | 25 | ### Twitter Follower Count Example 26 | The `twitter-follower-count/` directory demonstrates fetching social media metrics via authenticated APIs: 27 | - **`getFollowerCount.ts`** - Fetch real-time Twitter/X follower counts using Switchboard oracles 28 | - **Bearer Token authentication** - Simple app-only auth, no OAuth complexity 29 | - **No .env files** - Fully interactive with hidden token input for security 30 | - **Chain-agnostic** - Works across Solana, EVM, Sui, and any supported blockchain 31 | - **Full documentation** - See [twitter-follower-count/README.md](./twitter-follower-count/README.md) 32 | 33 | ## Why Job Testing is Chain-Agnostic 34 | 35 | Oracle job definitions use a universal format that works across all Switchboard-supported chains: 36 | - Same job definition syntax (HttpTask, JsonParseTask, etc.) 37 | - Same variable substitution patterns (`${API_KEY}`) 38 | - Same oracle consensus mechanism 39 | - Only the final oracle quote verification differs by chain 40 | 41 | This means you can: 42 | 1. **Design once, deploy everywhere** - Test your job definition here, use it on any chain 43 | 2. **Test without blockchain costs** - Validate API integrations before on-chain deployment 44 | 3. **Share job definitions** - Cross-chain applications can use identical oracle jobs 45 | 46 | ## Quick Start 47 | 48 | ### Variable Overrides 49 | ```bash 50 | # Test variable overrides with Polygon.io stock data 51 | cd common/variable-overrides 52 | POLYGON_API_KEY=your_key bun run testVariableOverrides.ts 53 | ``` 54 | 55 | ### Job Testing 56 | ```bash 57 | # Test a simple oracle job 58 | cd common/job-testing 59 | bun run runJob.ts 60 | 61 | # Test with real API integration 62 | POLYGON_API_KEY=your_key bun run runJob.ts 63 | ``` 64 | 65 | ### Streaming 66 | ```bash 67 | # Stream unsigned price data (requires API key) 68 | cd common/streaming 69 | SURGE_API_KEY=your_key bun run crossbarStream.ts 70 | ``` 71 | 72 | ### Twitter Follower Count 73 | ```bash 74 | # Fetch Twitter follower counts (interactive Bearer Token prompt) 75 | cd common/twitter-follower-count 76 | npm install 77 | npm start 78 | 79 | # Or specify a different username 80 | npm start username_here 81 | ``` 82 | 83 | ## Related Examples 84 | 85 | Once you've tested your job definitions, see chain-specific examples for implementation: 86 | - **Solana**: `../solana/examples/` - Oracle quotes, streaming, VRF 87 | - **EVM**: `../evm/` - Price feeds on EVM-compatible chains 88 | - **Sui**: `../sui/` - Pull-based oracle feeds 89 | 90 | ## Contributing 91 | 92 | When adding new chain-agnostic tools: 93 | 1. Ensure they work across all supported chains 94 | 2. Add comprehensive documentation 95 | 3. Include usage examples for each chain 96 | 4. Keep dependencies minimal and cross-platform compatible 97 | -------------------------------------------------------------------------------- /solana/legacy/feeds/view.ts: -------------------------------------------------------------------------------- 1 | // ANSI escape codes 2 | const HIDE_CURSOR = "\x1b[?25l"; 3 | const SHOW_CURSOR = "\x1b[?25h"; 4 | const ENTER_ALT_SCREEN = "\x1b[?1049h"; 5 | const EXIT_ALT_SCREEN = "\x1b[?1049l"; 6 | const MOVE_HOME = "\x1b[H"; 7 | const CLEAR_LINE = "\x1b[2K"; 8 | const BOLD = "\x1b[1m"; 9 | const RESET = "\x1b[0m"; 10 | const GREEN = "\x1b[32m"; 11 | const YELLOW = "\x1b[33m"; 12 | const CYAN = "\x1b[36m"; 13 | const RED = "\x1b[31m"; 14 | const DIM = "\x1b[2m"; 15 | const MAGENTA = "\x1b[35m"; 16 | 17 | export interface DisplayState { 18 | feedKey: string; 19 | feedValue: string | null; 20 | slot: number | null; 21 | error: string | null; 22 | logs: string[]; 23 | stats: { 24 | min: number; 25 | max: number; 26 | median: number; 27 | mean: number; 28 | count: number; 29 | } | null; 30 | lastUpdate: Date; 31 | status: "fetching" | "simulating" | "success" | "error"; 32 | } 33 | 34 | export function render(state: DisplayState): void { 35 | const { feedKey, feedValue, slot, error, logs, stats, lastUpdate, status } = state; 36 | 37 | const lines: string[] = []; 38 | 39 | // Header 40 | lines.push(`${BOLD}${CYAN}══════════════════════════════════════════════════════════════${RESET}`); 41 | lines.push(`${BOLD}${CYAN} SWITCHBOARD FEED MONITOR${RESET}`); 42 | lines.push(`${BOLD}${CYAN}══════════════════════════════════════════════════════════════${RESET}`); 43 | lines.push(""); 44 | 45 | 46 | // Feed Info 47 | lines.push(`${DIM}Feed:${RESET} ${feedKey}`); 48 | lines.push(`${DIM}Time:${RESET} ${lastUpdate.toLocaleTimeString()}`); 49 | const statusColor = status === "success" ? GREEN : status === "error" ? RED : YELLOW; 50 | lines.push(`${DIM}Status:${RESET} ${statusColor}${status.toUpperCase()}${RESET}`); 51 | lines.push(""); 52 | 53 | // Main Value or Error 54 | if (error) { 55 | lines.push(`${RED}${BOLD}ERROR:${RESET} ${error}`); 56 | lines.push(""); 57 | } else if (feedValue) { 58 | lines.push(`${BOLD}Value:${RESET} ${GREEN}${BOLD}${feedValue}${RESET}`); 59 | if (slot) { 60 | lines.push(`${DIM}Slot:${RESET} ${slot}`); 61 | } else { 62 | lines.push(""); 63 | } 64 | } else { 65 | lines.push(`${DIM}Value: Loading...${RESET}`); 66 | lines.push(""); 67 | } 68 | lines.push(""); 69 | 70 | // Stats 71 | lines.push(`${CYAN}${BOLD}─── Latency Stats ───${RESET}`); 72 | if (stats) { 73 | lines.push(`${DIM}Min:${RESET} ${stats.min} ms`); 74 | lines.push(`${DIM}Max:${RESET} ${stats.max} ms`); 75 | lines.push(`${DIM}Median:${RESET} ${stats.median} ms`); 76 | lines.push(`${DIM}Mean:${RESET} ${stats.mean.toFixed(2)} ms`); 77 | lines.push(`${DIM}Count:${RESET} ${stats.count}`); 78 | } else { 79 | lines.push(`${DIM}Waiting for data...${RESET}`); 80 | lines.push(""); 81 | lines.push(""); 82 | lines.push(""); 83 | lines.push(""); 84 | } 85 | lines.push(""); 86 | 87 | // Transaction Logs 88 | lines.push(`${CYAN}${BOLD}─── Transaction Logs ───${RESET}`); 89 | const maxLogs = 5; 90 | const displayLogs = logs.slice(-maxLogs); 91 | for (let i = 0; i < maxLogs; i++) { 92 | if (displayLogs[i]) { 93 | // Truncate long logs 94 | const log = displayLogs[i].length > 70 ? displayLogs[i].substring(0, 67) + "..." : displayLogs[i]; 95 | lines.push(`${DIM}${log}${RESET}`); 96 | } else { 97 | lines.push(""); 98 | } 99 | } 100 | 101 | lines.push(""); 102 | lines.push(`${DIM}Press Ctrl+C to exit${RESET}`); 103 | 104 | // Move to home and render 105 | process.stdout.write(MOVE_HOME + lines.map(l => CLEAR_LINE + l).join("\n")); 106 | } 107 | 108 | export function initScreen(): void { 109 | process.stdout.write(ENTER_ALT_SCREEN + HIDE_CURSOR); 110 | } 111 | 112 | export function cleanup(): void { 113 | process.stdout.write(SHOW_CURSOR + EXIT_ALT_SCREEN); 114 | } 115 | 116 | export function setupCleanupHandlers(): void { 117 | process.on("SIGINT", () => { 118 | cleanup(); 119 | process.exit(0); 120 | }); 121 | 122 | process.on("exit", cleanup); 123 | } 124 | -------------------------------------------------------------------------------- /common/streaming/README.md: -------------------------------------------------------------------------------- 1 | # Switchboard Streaming Examples 2 | 3 | Chain-agnostic examples for streaming real-time oracle data using Switchboard's Surge WebSocket service. 4 | 5 | ## What's Here 6 | 7 | ### `crossbarStream.ts` - Unsigned Price Streaming 8 | 9 | **Chain-Agnostic WebSocket Streaming** - Works with any blockchain 10 | 11 | A minimal example demonstrating how to stream unsigned price updates from Switchboard's Crossbar service: 12 | 13 | ```bash 14 | cd common/streaming 15 | SURGE_API_KEY=your_api_key bun run crossbarStream.ts 16 | ``` 17 | 18 | #### Features: 19 | - **Chain-agnostic** - No blockchain-specific dependencies 20 | - **WebSocket streaming** - Real-time price updates via Surge 21 | - **Unsigned data** - Suitable for UI/monitoring, not for on-chain use 22 | - **Ultra-low latency** - Millisecond-level price updates 23 | 24 | #### What it demonstrates: 25 | - Connecting to Switchboard Surge in Crossbar mode 26 | - Subscribing to specific price feeds 27 | - Receiving and displaying unsigned price updates 28 | - Measuring data latency 29 | 30 | #### Output Example: 31 | ``` 32 | Received unsigned price update for BTC/USD: 33 | BTC/USD (BINANCE): $64,234.56 | Latency: 42ms 34 | ``` 35 | 36 | ## Why Chain-Agnostic? 37 | 38 | This example uses only the `@switchboard-xyz/on-demand` package's Surge client, which works identically across all supported chains (Solana, EVM, Sui). The unsigned price stream is the same regardless of which blockchain you're building on. 39 | 40 | ## Use Cases 41 | 42 | - **Price monitoring dashboards** - Display real-time prices in UIs 43 | - **Trading interfaces** - Show live market data to users 44 | - **Analytics tools** - Track price movements and trends 45 | - **Testing** - Verify oracle data feeds are working 46 | 47 | ## Chain-Specific Examples 48 | 49 | For examples that integrate streaming data with on-chain transactions: 50 | 51 | - **Solana**: See `solana/examples/streaming/runSurge.ts` - Demonstrates signed price updates with Solana program integration 52 | - **Sui**: See `sui/examples/surge/surgeUpdate.ts` - Shows how to use Surge with Sui transactions 53 | - **EVM**: Coming soon 54 | 55 | ## Requirements 56 | 57 | ### API Key 58 | Get a Surge API key from [Switchboard Dashboard](https://explorer.switchboard.xyz) 59 | 60 | ### Environment Setup 61 | ```bash 62 | export SURGE_API_KEY="sb_live_your_api_key_here" 63 | ``` 64 | 65 | ### Installation 66 | ```bash 67 | # From repository root 68 | cd common/streaming 69 | bun install # or npm install 70 | ``` 71 | 72 | ## Technical Details 73 | 74 | ### Crossbar Mode 75 | 76 | This example uses Surge in "Crossbar mode", which streams unsigned price data: 77 | 78 | ```typescript 79 | const surge = new sb.Surge({ 80 | apiKey: apiKey, 81 | crossbarUrl: "https://crossbar.switchboardlabs.xyz", 82 | crossbarMode: true, // ← Enables unsigned streaming 83 | verbose: true, 84 | }); 85 | ``` 86 | 87 | **Unsigned vs Signed:** 88 | - **Unsigned** (this example): Fast, low-overhead, suitable for display 89 | - **Signed** (chain-specific): Cryptographically verified, required for on-chain use 90 | 91 | ### WebSocket Events 92 | 93 | The example listens for unsigned price updates: 94 | 95 | ```typescript 96 | surge.on("unsignedPriceUpdate", (update: sb.UnsignedPriceUpdate) => { 97 | const symbols = update.getSymbols(); 98 | const formattedPrices = update.getFormattedPrices(); 99 | // Display prices... 100 | }); 101 | ``` 102 | 103 | ## Next Steps 104 | 105 | 1. **Run the example** to see live price streaming 106 | 2. **Modify the symbols** in `crossbarStream.ts` to track different assets 107 | 3. **Explore chain-specific examples** to integrate prices into smart contracts 108 | 109 | ## Related Examples 110 | 111 | - **Job Testing**: `../job-testing/` - Test custom oracle job definitions 112 | - **Solana Streaming**: `../../solana/examples/streaming/` - Signed updates with Solana integration 113 | - **Sui Streaming**: `../../sui/examples/surge/surgeUpdate.ts` - Surge integration for Sui 114 | 115 | --- 116 | 117 | **Note**: This example streams unsigned data for monitoring purposes. For on-chain oracle data, use the chain-specific signed streaming examples. 118 | -------------------------------------------------------------------------------- /evm/randomness/coin-flip/src/CoinFlip.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import { ISwitchboard } from '@switchboard-xyz/on-demand-solidity/interfaces/ISwitchboard.sol'; 5 | import { SwitchboardTypes } from '@switchboard-xyz/on-demand-solidity/libraries/SwitchboardTypes.sol'; 6 | 7 | contract CoinFlip { 8 | 9 | // Wager data 10 | struct Wager { 11 | uint256 amount; 12 | address user; 13 | bytes32 randomnessId; 14 | uint256 flipTimestamp; 15 | } 16 | 17 | // Events 18 | event CoinFlipped(address indexed user, bytes32 randomnessId, uint256 amount); 19 | event FlipSettled(address indexed user, bool won, uint256 payout, uint256 randomValue); 20 | event SettlementFailed(address indexed user); 21 | 22 | // minimum flip amount 23 | uint256 public constant MIN_FLIP_AMOUNT = 1 ether; 24 | 25 | // Wager data mapping 26 | mapping(address => Wager) public wagers; 27 | 28 | // Switchboard Smart Contract 29 | ISwitchboard public switchboard; 30 | 31 | // Pass in the Switchboard Smart Contract address 32 | // when deploying the contract 33 | constructor(address _switchboard) { 34 | switchboard = ISwitchboard(_switchboard); 35 | } 36 | 37 | // do the flip 38 | function coinFlip() public payable { 39 | require(msg.value == MIN_FLIP_AMOUNT, "Must send exactly 1 ETH"); 40 | require(wagers[msg.sender].amount == 0, "Already flipped"); 41 | bytes32 randomnessId = keccak256(abi.encodePacked(msg.sender, blockhash(block.number - 1))); 42 | switchboard.createRandomness(randomnessId, 1); 43 | wagers[msg.sender] = Wager({ 44 | amount: msg.value, 45 | user: msg.sender, 46 | randomnessId: randomnessId, 47 | flipTimestamp: block.timestamp 48 | }); 49 | 50 | emit CoinFlipped(msg.sender, randomnessId, msg.value); 51 | } 52 | 53 | // settle the flip 54 | function settleFlip(bytes calldata encodedRandomness) public { 55 | // Check that the user has a pending wager 56 | Wager memory wager = wagers[msg.sender]; 57 | require(wager.amount > 0, "No pending wager"); 58 | 59 | // Try to settle the randomness 60 | try switchboard.settleRandomness(encodedRandomness) { 61 | // Check that randomness is resolved 62 | SwitchboardTypes.Randomness memory randomness = switchboard.getRandomness(wager.randomnessId); 63 | require(randomness.value != 0, "Randomness not resolved"); 64 | 65 | // Determine win/lose: even = win, odd = lose 66 | bool won = uint256(randomness.value) % 2 == 0; 67 | uint256 payout = 0; 68 | 69 | // Pay out the winner 70 | if (won) { 71 | payout = wager.amount * 2; 72 | (bool success, ) = wager.user.call{value: payout}(""); 73 | require(success, "Transfer failed"); 74 | } 75 | 76 | emit FlipSettled(wager.user, won, payout, randomness.value); 77 | 78 | } catch { 79 | // Settlement failed - treat as a loss and clear the wager 80 | // This could be due to oracle issues or malformed randomness 81 | emit SettlementFailed(msg.sender); 82 | } 83 | 84 | // Clear the wager regardless of outcome 85 | delete wagers[msg.sender]; 86 | } 87 | 88 | // View function to get the wager randomness ID 89 | function getWagerRandomnessId(address user) public view returns (bytes32) { 90 | return wagers[user].randomnessId; 91 | } 92 | 93 | // View function to get the assigned oracle, roll timestamp, and min settlement delay 94 | function getWagerData(address user) public view returns (address oracle, uint256 rollTimestamp, uint256 minSettlementDelay) { 95 | SwitchboardTypes.Randomness memory randomness = switchboard.getRandomness(wagers[user].randomnessId); 96 | return (randomness.oracle, randomness.rollTimestamp, randomness.minSettlementDelay); 97 | } 98 | 99 | 100 | // Just allow the contract to receive ETH 101 | receive() external payable {} 102 | } 103 | -------------------------------------------------------------------------------- /common/twitter-follower-count/getFollowerCount.ts: -------------------------------------------------------------------------------- 1 | import { CrossbarClient, OracleJob } from "@switchboard-xyz/common"; 2 | import { getAccessToken } from "./oauth.ts"; 3 | 4 | /** 5 | * Creates an Oracle Job definition to fetch Twitter/X follower count 6 | * @param username - Twitter/X username (without @) 7 | * @returns OracleJob configured to fetch follower count using OAuth 2.0 8 | */ 9 | function getTwitterFollowerCountJob(username: string): OracleJob { 10 | const job = OracleJob.fromObject({ 11 | tasks: [ 12 | { 13 | httpTask: { 14 | url: `https://api.twitter.com/2/users/by/username/${username}?user.fields=public_metrics`, 15 | method: "GET", 16 | headers: [ 17 | { 18 | key: "Authorization", 19 | value: "Bearer ${TWITTER_ACCESS_TOKEN}", 20 | }, 21 | ], 22 | }, 23 | }, 24 | { 25 | jsonParseTask: { 26 | path: "$.data.public_metrics.followers_count", 27 | }, 28 | }, 29 | ], 30 | }); 31 | return job; 32 | } 33 | 34 | 35 | /** 36 | * Main function to fetch Twitter follower count using Switchboard oracle 37 | */ 38 | (async function main() { 39 | // Get username from command line args or use default 40 | const username = process.argv[2] || "elonmusk"; 41 | 42 | // Run OAuth flow or prompt for token 43 | const accessToken = await getAccessToken(); 44 | 45 | // Mask token for logging (show first 5 chars only) 46 | const maskedToken = accessToken.slice(0, 5) + "***"; 47 | 48 | // Initialize Crossbar client (chain-agnostic) 49 | const crossbarClient = CrossbarClient.default(); 50 | 51 | // Build oracle job with username 52 | const job = getTwitterFollowerCountJob(username); 53 | 54 | // Create an OracleFeed with the job 55 | const feed = { 56 | name: `Twitter Follower Count - @${username}`, 57 | jobs: [job], 58 | }; 59 | 60 | console.log("⏳ Requesting data from Switchboard oracles...\n"); 61 | 62 | const result = await crossbarClient.simulateFeed( 63 | feed, 64 | false, // includeReceipts 65 | { TWITTER_ACCESS_TOKEN: accessToken } 66 | ); 67 | 68 | console.log("✅ Successfully fetched follower count!\n"); 69 | 70 | // Parse and display results 71 | const followerCount = result.results?.[0]; 72 | 73 | if (followerCount === undefined || followerCount === null) { 74 | throw new Error("Failed to fetch follower count from oracle. Response: " + JSON.stringify(result)); 75 | } 76 | 77 | // Convert to number (oracle returns as number but TypeScript sees it as unknown) 78 | const followerCountNum = Number(followerCount); 79 | 80 | console.log("═══════════════════════════════════════"); 81 | console.log(` Username: @${username}`); 82 | console.log(` Followers: ${Math.floor(followerCountNum).toLocaleString()}`); 83 | console.log("═══════════════════════════════════════\n"); 84 | 85 | // Display additional oracle metadata 86 | console.log("📊 Oracle Response Metadata:"); 87 | console.log(` Raw Value: ${followerCount}`); 88 | console.log(` Token Used: ${maskedToken}`); 89 | console.log(` Timestamp: ${new Date().toISOString()}\n`); 90 | 91 | })().catch((error) => { 92 | console.error("\n❌ Error fetching follower count:"); 93 | 94 | // Mask any tokens in error messages 95 | let errorMessage = error.message || String(error); 96 | // Replace any long alphanumeric strings that look like tokens 97 | errorMessage = errorMessage.replace(/[A-Za-z0-9]{20,}/g, (match) => { 98 | return match.slice(0, 5) + "***"; 99 | }); 100 | 101 | console.error(errorMessage); 102 | 103 | if (error.message?.includes("404")) { 104 | console.error("\n💡 Tip: Make sure the username is correct and the account exists.\n"); 105 | } else if (error.message?.includes("401") || error.message?.includes("403")) { 106 | console.error("\n💡 Tip: Your Twitter API token may be invalid or expired."); 107 | console.error(" Generate a new token at: https://developer.twitter.com/en/portal/dashboard\n"); 108 | } else if (error.message?.includes("429")) { 109 | console.error("\n💡 Tip: You've hit Twitter's rate limit. Wait a few minutes and try again.\n"); 110 | } 111 | 112 | process.exit(1); 113 | }); 114 | -------------------------------------------------------------------------------- /evm/legacy/examples/updateFeed.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Example demonstrating how to fetch and update Switchboard oracle prices on EVM chains 3 | * 4 | * This script shows: 5 | * - Fetching signed price data from Crossbar 6 | * - Submitting oracle updates to your contract 7 | * - Reading and parsing updated feed values 8 | * - Event parsing and logging 9 | * 10 | * Run with: bun run examples/updateFeed.ts 11 | */ 12 | 13 | import * as ethers from "ethers"; 14 | import { CrossbarClient } from "@switchboard-xyz/common"; 15 | 16 | (async function main() { 17 | // Parse the response as JSON 18 | const secret = process.env.PRIVATE_KEY as string; 19 | if (!secret) { 20 | throw new Error("No private key provided"); 21 | } 22 | 23 | // Create a provider 24 | const provider = new ethers.JsonRpcProvider( 25 | "https://rpc.hyperliquid.xyz/evm" 26 | ); 27 | 28 | // Create a signer 29 | const signerWithProvider = new ethers.Wallet(secret, provider); 30 | 31 | // Target contract address (your deployed Example contract) 32 | const exampleAddress = process.env.EXAMPLE_ADDRESS as string; 33 | if (!exampleAddress) { 34 | throw new Error("No example contract address provided"); 35 | } 36 | 37 | // for tokens (this is the Human-Readable ABI format) 38 | const abi = [ 39 | "function getFeedData(bytes[] calldata updates) public payable", 40 | "function aggregatorId() public view returns (bytes32)", 41 | "function latestPrice() public view returns (int256)", 42 | "function lastUpdateTimestamp() public view returns (uint256)", 43 | "function lastOracleId() public view returns (bytes32)", 44 | "function getLatestUpdate() external view returns (int128 result, uint256 timestamp, bytes32 oracleId)", 45 | "event FeedData(int128 price, uint256 timestamp, bytes32 oracleId)", 46 | ]; 47 | 48 | const crossbar = new CrossbarClient(`https://crossbar.switchboard.xyz`); 49 | 50 | // The Contract object 51 | const exampleContract = new ethers.Contract( 52 | exampleAddress, 53 | abi, 54 | signerWithProvider 55 | ); 56 | 57 | // Get the aggregator ID from the contract 58 | const aggregatorId = await exampleContract.aggregatorId(); 59 | console.log("Aggregator ID:", aggregatorId); 60 | 61 | // Get the encoded updates 62 | const { encoded } = await crossbar.fetchEVMResults({ 63 | chainId: 999, // Use the correct chain ID for your network 64 | aggregatorIds: [aggregatorId], 65 | }); 66 | 67 | console.log("Encoded updates length:", encoded.length); 68 | 69 | // Update the contract + do some business logic 70 | const tx = await exampleContract.getFeedData(encoded); 71 | 72 | console.log("Transaction hash:", tx.hash); 73 | 74 | // Wait for transaction confirmation 75 | const receipt = await tx.wait(); 76 | console.log("Transaction confirmed in block:", receipt.blockNumber); 77 | 78 | // Parse events from the transaction 79 | if (receipt.logs) { 80 | const iface = new ethers.Interface(abi); 81 | for (const log of receipt.logs) { 82 | try { 83 | const parsed = iface.parseLog({ topics: log.topics, data: log.data }); 84 | if (parsed && parsed.name === "FeedData") { 85 | console.log("\n=== Feed Update Event ==="); 86 | console.log("Price:", parsed.args.price.toString()); 87 | console.log("Timestamp:", new Date(Number(parsed.args.timestamp) * 1000).toISOString()); 88 | console.log("Oracle ID:", parsed.args.oracleId); 89 | } 90 | } catch (e) { 91 | // Skip logs that don't match our interface 92 | } 93 | } 94 | } 95 | 96 | // Get detailed update information 97 | const [result, timestamp, oracleId] = await exampleContract.getLatestUpdate(); 98 | console.log("\n=== Latest Update Details ==="); 99 | console.log("Result:", result.toString()); 100 | console.log("Timestamp:", new Date(Number(timestamp) * 1000).toISOString()); 101 | console.log("Oracle ID:", oracleId); 102 | 103 | // Also log the individual values for backwards compatibility 104 | console.log("\n=== Individual Contract Values ==="); 105 | console.log("Latest Price:", await exampleContract.latestPrice()); 106 | console.log("Last Update Timestamp:", await exampleContract.lastUpdateTimestamp()); 107 | console.log("Last Oracle ID:", await exampleContract.lastOracleId()); 108 | }(); 109 | -------------------------------------------------------------------------------- /solana/legacy/variable-overrides/scripts/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { OracleJob } from "@switchboard-xyz/common"; 2 | import * as anchor from "@coral-xyz/anchor"; 3 | import { 4 | Connection, 5 | Keypair, 6 | PublicKey, 7 | VersionedTransaction, 8 | TransactionSignature, 9 | Commitment, 10 | } from "@solana/web3.js"; 11 | import * as sb from "@switchboard-xyz/on-demand"; 12 | 13 | export const DEMO_PATH = "target/deploy/sb_on_demand_solana-keypair.json"; 14 | export const TX_CONFIG = { 15 | commitment: "processed" as Commitment, 16 | skipPreflight: true, 17 | maxRetries: 0, 18 | }; 19 | 20 | export async function myProgramIx(program: anchor.Program, feed: PublicKey) { 21 | return await program.methods.test().accounts({ feed }).instruction(); 22 | } 23 | 24 | export async function myAnchorProgram( 25 | provider: anchor.Provider, 26 | keypath: string 27 | ): Promise { 28 | try { 29 | const myProgramKeypair = await sb.AnchorUtils.initKeypairFromFile(keypath); 30 | const pid = myProgramKeypair.publicKey; 31 | const idl = (await anchor.Program.fetchIdl(pid, provider))!; 32 | const program = new anchor.Program(idl, provider); 33 | return program; 34 | } catch (e) { 35 | throw new Error("Failed to load demo program. Was it deployed?"); 36 | } 37 | } 38 | 39 | export async function sendAndConfirmTx( 40 | connection: Connection, 41 | tx: VersionedTransaction, 42 | signers: Array 43 | ): Promise { 44 | tx.sign(signers); 45 | const sig = await connection.sendTransaction(tx); 46 | await connection.confirmTransaction(sig, "confirmed"); 47 | return sig; 48 | } 49 | 50 | /** 51 | * Builds an Oracle job using variable overrides instead of secrets. 52 | * Following security best practices from the gitbook documentation: 53 | * - Only use variables for API keys/authentication (✅) 54 | * - Hardcode all data sources, paths, and parameters (✅) 55 | * - Ensure feed verifiability by making data extraction deterministic (✅) 56 | */ 57 | export function buildVariableOverrideJob(): OracleJob { 58 | const jobConfig = { 59 | tasks: [ 60 | { 61 | httpTask: { 62 | // ✅ Hardcoded endpoint and parameters - fully verifiable data source 63 | // Only the API key uses variable substitution for secure credential management 64 | url: "https://api.openweathermap.org/data/2.5/weather?q=aspen,us&appid=${OPEN_WEATHER_API_KEY}&units=metric", 65 | method: "GET", 66 | }, 67 | }, 68 | { 69 | jsonParseTask: { 70 | // ✅ Hardcoded path - verifiable data extraction 71 | path: "$.main.temp", 72 | }, 73 | }, 74 | ], 75 | }; 76 | return OracleJob.fromObject(jobConfig); 77 | } 78 | 79 | /** 80 | * Creates a simple value job for testing variable overrides 81 | */ 82 | export function buildSimpleValueJob(): OracleJob { 83 | const jobConfig = { 84 | tasks: [ 85 | { 86 | valueTask: { 87 | big: "${TEST_VALUE}", 88 | }, 89 | }, 90 | ], 91 | }; 92 | return OracleJob.fromObject(jobConfig); 93 | } 94 | 95 | /** 96 | * Example of a more complex job with multiple API authentication headers 97 | * Following security best practices - only auth tokens use variables 98 | */ 99 | export function buildMultiAuthJob(): OracleJob { 100 | const jobConfig = { 101 | tasks: [ 102 | { 103 | httpTask: { 104 | // ✅ Hardcoded endpoint - verifiable data source 105 | url: "https://api.polygon.io/v2/last/trade/AAPL", // Hardcoded symbol for verifiability 106 | method: "GET", 107 | headers: [ 108 | { 109 | key: "Authorization", 110 | value: "Bearer ${AUTH_TOKEN}", // ✅ Only auth token as variable 111 | }, 112 | { 113 | key: "X-API-Key", 114 | value: "${API_KEY}", // ✅ Only API key as variable 115 | }, 116 | { 117 | key: "User-Agent", 118 | value: "Switchboard-Oracle/1.0", // ✅ Hardcoded 119 | }, 120 | ], 121 | }, 122 | }, 123 | { 124 | jsonParseTask: { 125 | // ✅ Hardcoded path - verifiable data extraction 126 | path: "$.results.p", 127 | }, 128 | }, 129 | ], 130 | }; 131 | return OracleJob.fromObject(jobConfig); 132 | } -------------------------------------------------------------------------------- /evm/randomness/pancake-stacker/scripts/stack-pancake.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { CrossbarClient } from "@switchboard-xyz/common"; 3 | 4 | // defines interface for the pancake flipper contract we interact with 5 | const PANCAKE_STACKER_ABI = [ 6 | "function flipPancake() public", 7 | "function catchPancake(bytes calldata encodedRandomness) public", 8 | "function getFlipData(address user) public view returns (bytes32 randomnessId, address oracle, uint256 rollTimestamp, uint256 minSettlementDelay)", 9 | "function getPlayerStats(address user) public view returns (uint256 currentStack, bool hasPendingFlip)", 10 | "event PancakeFlipRequested(address indexed user, bytes32 randomnessId)", 11 | "event PancakeLanded(address indexed user, uint256 newStackHeight)", 12 | "event StackKnockedOver(address indexed user)", 13 | "event SettlementFailed(address indexed user)", 14 | ]; 15 | 16 | async function main() { 17 | 18 | // load your private key for your wallet 19 | const privateKey = process.env.PRIVATE_KEY; 20 | if (!privateKey) { 21 | throw new Error("PRIVATE_KEY is not set"); 22 | } 23 | 24 | // load on-chain contract address 25 | const contractAddress = process.env.PANCAKE_STACKER_CONTRACT_ADDRESS; 26 | if (!contractAddress) { 27 | throw new Error("PANCAKE_STACKER_CONTRACT_ADDRESS is not set"); 28 | } 29 | 30 | // initialize RPC, wallet, crossbar server 31 | const provider = new ethers.JsonRpcProvider("https://rpc.monad.xyz"); 32 | const wallet = new ethers.Wallet(privateKey, provider); 33 | const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz"); 34 | 35 | // initialize contract instance to call 36 | const contract = new ethers.Contract(contractAddress, PANCAKE_STACKER_ABI, wallet); 37 | 38 | // call contract to get current stats before flip 39 | const [currentStack] = await contract.getPlayerStats(wallet.address); 40 | console.log(`\nCurrent stack: ${currentStack} pancakes`); 41 | 42 | // call contract to flip a pancake 43 | console.log("\nFlipping pancake..."); 44 | const tx = await contract.flipPancake(); 45 | await tx.wait(); 46 | console.log("Flip requested:", tx.hash); 47 | 48 | // get data of the randomness you requested 49 | const flipData = await contract.getFlipData(wallet.address); 50 | 51 | // ask crossbar to talk to oracle and retrieve randomness 52 | console.log("Resolving randomness..."); 53 | const { encoded } = await crossbar.resolveEVMRandomness({ 54 | chainId: 143, 55 | randomnessId: flipData.randomnessId, 56 | timestamp: Number(flipData.rollTimestamp), 57 | minStalenessSeconds: Number(flipData.minSettlementDelay), 58 | oracle: flipData.oracle, 59 | }); 60 | 61 | // Call contract with randomness to catch the pancake 62 | console.log("Catching pancake..."); 63 | const tx2 = await contract.catchPancake(encoded); 64 | const receipt = await tx2.wait(); 65 | 66 | // Parse events to get the result 67 | for (const log of receipt.logs) { 68 | try { 69 | const parsed = contract.interface.parseLog(log); 70 | 71 | if (parsed?.name === "PancakeLanded") { 72 | const { newStackHeight } = parsed.args; 73 | console.log("\n========================================"); 74 | console.log("PANCAKE LANDED!"); 75 | console.log(`Stack height: ${newStackHeight} pancakes`); 76 | console.log("========================================\n"); 77 | } 78 | 79 | if (parsed?.name === "StackKnockedOver") { 80 | console.log("\n========================================"); 81 | console.log("STACK KNOCKED OVER!"); 82 | console.log("========================================\n"); 83 | } 84 | 85 | if (parsed?.name === "SettlementFailed") { 86 | console.log("\n========================================"); 87 | console.log("SETTLEMENT FAILED!"); 88 | console.log("Stack reset due to oracle/randomness issue"); 89 | console.log("========================================\n"); 90 | } 91 | } catch {} 92 | } 93 | 94 | // Show final stats 95 | const [finalStack] = await contract.getPlayerStats(wallet.address); 96 | console.log(`Your stack: ${finalStack} pancakes`); 97 | } 98 | 99 | main(); 100 | -------------------------------------------------------------------------------- /evm/randomness/pancake-stacker/src/PancakeStacker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import { ISwitchboard } from '@switchboard-xyz/on-demand-solidity/interfaces/ISwitchboard.sol'; 5 | import { SwitchboardTypes } from '@switchboard-xyz/on-demand-solidity/libraries/SwitchboardTypes.sol'; 6 | 7 | contract PancakeStacker { 8 | 9 | // Events 10 | event PancakeFlipRequested(address indexed user, bytes32 randomnessId); 11 | event PancakeLanded(address indexed user, uint256 newStackHeight); 12 | event StackKnockedOver(address indexed user); 13 | event SettlementFailed(address indexed user); 14 | 15 | // Pending flip randomness ID for each user (bytes32(0) = no pending flip) 16 | mapping(address => bytes32) public pendingFlips; 17 | 18 | // Current stack height for each player 19 | mapping(address => uint256) public stackHeight; 20 | 21 | // declare and initialize switchboard as a parameter 22 | // that holds the switchboard contract address 23 | ISwitchboard public switchboard; 24 | 25 | constructor(address _switchboard) { 26 | switchboard = ISwitchboard(_switchboard); 27 | } 28 | 29 | // Flip a pancake onto the stack 30 | function flipPancake() public { 31 | // check no pending flip exists 32 | require(pendingFlips[msg.sender] == bytes32(0), "Already have pending flip"); 33 | 34 | // generate randomnessID with sender address and last blockhash 35 | bytes32 randomnessId = keccak256(abi.encodePacked(msg.sender, blockhash(block.number - 1))); 36 | 37 | // ask switchboard contract to create a new randomness request 38 | // with 1 second settlement delay 39 | switchboard.createRandomness(randomnessId, 1); 40 | 41 | // store the randomness request as a pending flip for the sender 42 | pendingFlips[msg.sender] = randomnessId; 43 | 44 | emit PancakeFlipRequested(msg.sender, randomnessId); 45 | } 46 | 47 | // Catch the pancake and see if it lands 48 | function catchPancake(bytes calldata encodedRandomness) public { 49 | 50 | // make sure caller has a pending flip 51 | bytes32 randomnessId = pendingFlips[msg.sender]; 52 | require(randomnessId != bytes32(0), "No pending flip"); 53 | 54 | // give the randomness object to the switchboard contract 55 | // and ask it to verify that it's correct 56 | try switchboard.settleRandomness(encodedRandomness) { 57 | 58 | // verification succeeded, now get the randomness value 59 | SwitchboardTypes.Randomness memory randomness = switchboard.getRandomness(randomnessId); 60 | 61 | // 2/3 chance to land (roll 0-1), 1/3 chance to knock over 62 | bool landed = uint256(randomness.value) % 3 < 2; 63 | 64 | if (landed) { 65 | // Pancake lands! Increment stack 66 | stackHeight[msg.sender]++; 67 | emit PancakeLanded(msg.sender, stackHeight[msg.sender]); 68 | } else { 69 | // Stack knocked over! 70 | stackHeight[msg.sender] = 0; 71 | emit StackKnockedOver(msg.sender); 72 | } 73 | 74 | // Clear the request 75 | delete pendingFlips[msg.sender]; 76 | 77 | // if switchboard failed to parse the encoded randomness 78 | } catch { 79 | // Caution: could be an issue with the oracle or malformed randomness for another reason 80 | // to be safe we make the player reset their stack and start over 81 | stackHeight[msg.sender] = 0; 82 | delete pendingFlips[msg.sender]; 83 | emit StackKnockedOver(msg.sender); 84 | emit SettlementFailed(msg.sender); 85 | } 86 | } 87 | 88 | // View function to get data needed for off-chain resolution 89 | function getFlipData(address user) public view returns (bytes32 randomnessId, address oracle, uint256 rollTimestamp, uint256 minSettlementDelay) { 90 | randomnessId = pendingFlips[user]; 91 | SwitchboardTypes.Randomness memory randomness = switchboard.getRandomness(randomnessId); 92 | return (randomnessId, randomness.oracle, randomness.rollTimestamp, randomness.minSettlementDelay); 93 | } 94 | 95 | // Get player stats 96 | function getPlayerStats(address user) public view returns (uint256 currentStack, bool hasPendingFlip) { 97 | return ( 98 | stackHeight[user], 99 | pendingFlips[user] != bytes32(0) 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /solana/feeds/basic/scripts/managedUpdate.ts: -------------------------------------------------------------------------------- 1 | import * as sb from "@switchboard-xyz/on-demand"; 2 | import { OracleQuote, isMainnetConnection } from "@switchboard-xyz/on-demand"; 3 | import yargs from "yargs"; 4 | import * as fs from "fs"; 5 | import { 6 | TX_CONFIG, 7 | loadBasicProgram, 8 | basicReadOracleIx, 9 | BASIC_PROGRAM_PATH, 10 | DEFAULT_FEED_ID, 11 | logFeedId, 12 | handleSimulationError, 13 | } from "@/utils"; 14 | 15 | const argv = yargs(process.argv) 16 | .options({ 17 | feedId: { 18 | type: "string", 19 | required: false, 20 | default: DEFAULT_FEED_ID, 21 | description: "The hexadecimal ID of the price feed (get from Switchboard Explorer)", 22 | }, 23 | }) 24 | .parseSync(); 25 | 26 | /** 27 | * Basic Managed Oracle Update Example 28 | * 29 | * This example demonstrates the simplest way to use Switchboard's new managed update system 30 | * with the quote program. It shows how to: 31 | * 32 | * 1. Auto-detect network (mainnet/devnet) and choose appropriate queue 33 | * 2. Derive the canonical oracle account from feed hashes 34 | * 3. Use fetchManagedUpdateIxs to get both Ed25519 and quote program instructions 35 | * 4. Create a program instruction that reads from the managed oracle account 36 | * 5. Execute the transaction to update and consume oracle data 37 | * 38 | * The managed update system handles: 39 | * - Automatic network detection and queue selection 40 | * - Oracle account creation (if needed) 41 | * - Quote verification and storage 42 | * - Automatic account derivation 43 | * - Optimized instruction generation 44 | */ 45 | (async function main() { 46 | // Load Solana environment configuration 47 | // Note: loadEnv automatically sets the crossbar network based on the detected RPC connection 48 | const { program, keypair, connection, crossbar, queue, isMainnet } = 49 | await sb.AnchorUtils.loadEnv(); 50 | 51 | logFeedId(argv.feedId); 52 | console.log("🌐 Queue selected:", queue.pubkey.toBase58()); 53 | console.log("🔧 Crossbar network:", crossbar.getNetwork()); 54 | 55 | // Step 1: Derive the canonical oracle account from feed hashes 56 | // This uses the same derivation logic as the quote program 57 | const [quoteAccount] = OracleQuote.getCanonicalPubkey(queue.pubkey, [argv.feedId]); 58 | console.log("📍 Quote Account (derived):", quoteAccount.toBase58()); 59 | 60 | // Simulate the feed - automatically uses the network configured in loadEnv 61 | const simFeed = await crossbar.simulateFeed(argv.feedId); 62 | console.log(simFeed); 63 | 64 | // Step 2: Create managed update instructions 65 | // This returns both the Ed25519 signature verification instruction 66 | // and the quote program instruction that stores the verified data 67 | const instructions = await queue.fetchManagedUpdateIxs( 68 | crossbar, 69 | [argv.feedId], 70 | { 71 | variableOverrides: {}, 72 | instructionIdx: 0, // Ed25519 instruction index 73 | payer: keypair.publicKey, 74 | } 75 | ); 76 | 77 | console.log("✨ Generated instructions:", instructions.length); 78 | console.log(" - Ed25519 signature verification"); 79 | console.log(" - Quote program verified_update"); 80 | 81 | // Step 3: Create your program instruction to read the oracle data 82 | // This instruction will read from the quote account that was just updated 83 | // Load the basic oracle example program 84 | const ixs = [...instructions]; 85 | 86 | if (fs.existsSync(BASIC_PROGRAM_PATH)) { 87 | const basicProgram = await loadBasicProgram(program!.provider); 88 | const readOracleIx = await basicReadOracleIx( 89 | basicProgram, 90 | quoteAccount, 91 | queue.pubkey, 92 | keypair.publicKey 93 | ); 94 | ixs.push(readOracleIx); 95 | console.log(" - Basic oracle program crank instruction"); 96 | } else { 97 | console.log("ℹ️ Skipping crank: basic_oracle_example program not deployed"); 98 | console.log(" To deploy, run: anchor build && anchor deploy"); 99 | } 100 | 101 | // Step 4: Build and send the transaction 102 | const tx = await sb.asV0Tx({ 103 | connection, 104 | ixs, 105 | signers: [keypair], 106 | computeUnitPrice: 20_000, // Priority fee 107 | computeUnitLimitMultiple: 1.1, // 10% buffer 108 | }); 109 | 110 | // Send the transaction 111 | try { 112 | const sim = await connection.simulateTransaction(tx); 113 | console.log(sim.value.logs?.join("\n")); 114 | if (sim.value.err) { 115 | await handleSimulationError(sim.value.err, connection, keypair.publicKey); 116 | return; 117 | } 118 | } catch (error) { 119 | console.error("❌ Transaction failed:", error); 120 | } 121 | })(); 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![Switchboard Logo](https://github.com/switchboard-xyz/core-sdk/raw/main/website/static/img/icons/switchboard/avatar.png) 4 | 5 | # Switchboard On-Demand Examples 6 | Example repositories for Switchboard's latest on-demand functionality across multiple blockchain ecosystems. 7 | 8 |
9 | 10 | ## Quick Links 11 | 12 | - **[User Guide and Technical Documentation](https://docs.switchboard.xyz/tooling-and-resources/technical-resources-and-documentation)** - Detailed information on getting started, usage examples, and API references. 13 | - **[Common TypeDoc Documentation](https://switchboardxyz-common.netlify.app/)** - TypeDoc for @switchboard-xyz/common shared utilities. 14 | - **[Feed Builder](https://explorer.switchboardlabs.xyz/feed-builder)** - Build and verify custom price feeds with checksum validation. 15 | - **[Feed Builder Documentation](https://explorer.switchboardlabs.xyz/task-docs)** - Documentation for oracle tasks and feed building. 16 | - **[Explorer](https://explorer.switchboard.xyz)** - Browse feeds, verify integrity, and view historical data. 17 | 18 | ## 🌐 Examples by Chain 19 | 20 | ### Solana 21 | 22 | The Solana examples demonstrate Switchboard On-Demand functionality on the Solana blockchain: 23 | 24 | - **[📊 On-Demand Feeds](./solana)** - Price feeds and data oracles 25 | - **[🎲 On-Demand Randomness](./solana/examples/randomness)** - Verifiable Random Function (VRF) 26 | - **[🔧 Variable Overrides](./common/variable-overrides)** - Secure credential management with variable substitution 27 | 28 | **JavaScript/TypeScript Client Code:** 29 | - **[📁 Client Examples](./solana/examples/)** - Complete JavaScript/TypeScript examples for integrating Switchboard On-Demand 30 | - Feed operations, streaming, benchmarks, and utilities 31 | - Ready-to-run examples for oracle quotes, Surge WebSocket streaming, and more 32 | 33 | **Resources:** 34 | - [Rust Crate](https://crates.io/crates/switchboard-on-demand) 35 | - [Solana SDK](https://www.npmjs.com/package/@switchboard-xyz/on-demand) 36 | - [TypeDoc Documentation](https://switchboard-docs.web.app/) 37 | 38 | ### Sui 39 | 40 | The Sui examples demonstrate Switchboard On-Demand oracle functionality on the Sui blockchain: 41 | 42 | - **[🔮 Oracle Feeds](./sui)** - Pull-based price feeds with fresh oracle data 43 | - Real-time data fetching from external APIs through oracle networks 44 | - On-demand feed updates with aggregated results from multiple oracles 45 | 46 | **JavaScript/TypeScript Client Code:** 47 | - **[📁 Feed Examples](./sui/examples/)** - Complete TypeScript examples for Sui integration 48 | - Oracle data fetching, feed reading, transaction simulation 49 | - No private key required for data reading and simulation 50 | 51 | **Resources:** 52 | - [Sui Documentation](https://docs.switchboard.xyz/product-documentation/data-feeds/sui) 53 | - [Sui SDK](https://www.npmjs.com/package/@switchboard-xyz/sui-sdk) 54 | 55 | ### EVM 56 | 57 | The EVM examples showcase Switchboard functionality on EVM-compatible chains: 58 | 59 | - **[📈 Price Feeds](./evm)** - Real-time price data for DeFi applications 60 | - **[🎲 On-Demand Randomness](./evm/examples/randomness.ts)** - Verifiable Random Function (VRF) 61 | 62 | **Supported Networks:** 63 | - Monad (Mainnet & Testnet) 64 | - Hyperliquid (Mainnet) 65 | 66 | **Resources:** 67 | - [EVM Documentation](https://docs.switchboard.xyz/product-documentation/data-feeds/evm) 68 | - [Solidity SDK](https://www.npmjs.com/package/@switchboard-xyz/on-demand-solidity) 69 | 70 | ## 🌍 Chain-Agnostic Resources 71 | 72 | The **[`common/`](./common/)** directory contains tools and examples that work across all blockchain platforms: 73 | 74 | - **[Variable Overrides](./common/variable-overrides/)** - Secure credential management for oracle feeds 75 | - Use variables for API keys/auth tokens only 76 | - Maintain feed verifiability with hardcoded data sources 77 | - Works identically on Solana, EVM, and Sui 78 | 79 | - **[Job Testing](./common/job-testing/)** - Test and develop custom oracle job definitions 80 | - Works identically on Solana, EVM, and Sui 81 | - Validate API integrations before on-chain deployment 82 | 83 | - **[Streaming](./common/streaming/)** - Real-time unsigned price streaming via WebSocket 84 | - Chain-agnostic price monitoring for UIs and dashboards 85 | - Ultra-low latency data feeds 86 | 87 | These resources let you design and test oracle functionality once, then deploy on any supported blockchain. 88 | 89 | ## 🚀 Getting Started 90 | 91 | Each directory contains specific examples with their own setup instructions. Choose your blockchain platform above to explore the relevant examples. 92 | 93 | For comprehensive documentation and integration guides, visit our [official documentation](https://docs.switchboard.xyz/) 94 | -------------------------------------------------------------------------------- /solana/feeds/advanced/programs/advanced-oracle-example/src/lib.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{entrypoint, msg, ProgramResult}; 2 | use switchboard_on_demand::{ 3 | QuoteVerifier, check_pubkey_eq, OracleQuote, Instructions, get_slot 4 | }; 5 | use pinocchio::account_info::AccountInfo; 6 | use pinocchio::program_error::ProgramError; 7 | use pinocchio::pubkey::Pubkey; 8 | 9 | mod utils; 10 | use utils::{init_quote_account_if_needed, init_state_account_if_needed}; 11 | 12 | entrypoint!(process_instruction); 13 | 14 | /// Advanced Oracle Example Program 15 | /// 16 | /// This program demonstrates parsing and displaying oracle feed data. 17 | /// It shows how to iterate through multiple feeds and extract their 18 | /// IDs and values for processing. 19 | #[inline(always)] 20 | pub fn process_instruction( 21 | program_id: &Pubkey, 22 | accounts: &[AccountInfo], 23 | instruction_data: &[u8], 24 | ) -> ProgramResult { 25 | match instruction_data[0] { 26 | 0 => crank(program_id, accounts)?, 27 | 1 => read(program_id, accounts)?, 28 | 2 => init_state(program_id, accounts)?, 29 | 3 => init_oracle(program_id, accounts)?, 30 | _ => return Err(ProgramError::InvalidInstructionData), 31 | } 32 | 33 | Ok(()) 34 | } 35 | 36 | #[inline(always)] 37 | pub fn crank(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 38 | let [quote, queue, state, payer, instructions_sysvar, _clock_sysvar]: &[AccountInfo; 6] = 39 | accounts.try_into().map_err(|_| ProgramError::NotEnoughAccountKeys)?; 40 | 41 | if !is_state_account(state, program_id) { 42 | msg!("Invalid state account"); 43 | return Err(ProgramError::Custom(2)); // InvalidStateAccount 44 | } 45 | 46 | // Simple state management - store authorized signer 47 | let state_data = unsafe { state.borrow_data_unchecked() }; 48 | 49 | if !check_pubkey_eq(&state_data, payer.key()) { 50 | // Signer mismatch, reject 51 | return Err(ProgramError::Custom(1)); // UnauthorizedSigner 52 | } 53 | 54 | // DANGER: only use this if you trust the signer and all accounts passed in this tx 55 | OracleQuote::write_from_ix_unchecked(instructions_sysvar, quote, queue.key(), 0); 56 | 57 | Ok(()) 58 | } 59 | 60 | #[inline(always)] 61 | pub fn read(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 62 | let [quote, queue, clock_sysvar, slothashes_sysvar, instructions_sysvar]: &[AccountInfo; 5] = 63 | accounts.try_into().map_err(|_| ProgramError::NotEnoughAccountKeys)?; 64 | 65 | let slot = get_slot(clock_sysvar); 66 | 67 | let quote_data = QuoteVerifier::new() 68 | .slothash_sysvar(slothashes_sysvar) 69 | .ix_sysvar(instructions_sysvar) 70 | .clock_slot(slot) 71 | .queue(queue) 72 | .max_age(30) 73 | .verify_account(quote) 74 | .unwrap(); 75 | 76 | msg!("Quote slot: {}", quote_data.slot()); 77 | 78 | // Parse and display each feed 79 | for (index, feed_info) in quote_data.feeds().iter().enumerate() { 80 | msg!("📋 Feed #{}: {}", index + 1, feed_info.hex_id()); 81 | msg!("💰 Value: {}", feed_info.value()); 82 | } 83 | 84 | Ok(()) 85 | } 86 | 87 | #[inline(always)] 88 | pub fn is_state_account(account: &AccountInfo, program_id: &Pubkey) -> bool { 89 | check_pubkey_eq(account.owner(), program_id) && account.data_len() == 32 90 | } 91 | 92 | #[inline(always)] 93 | pub fn init_state(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 94 | let [state, payer, system_program]: &[AccountInfo; 3] = 95 | accounts.try_into().map_err(|_| ProgramError::NotEnoughAccountKeys)?; 96 | 97 | init_state_account_if_needed( 98 | program_id, 99 | state, 100 | payer, 101 | system_program, 102 | )?; 103 | 104 | state.try_borrow_mut_data()?[..32].copy_from_slice(payer.key().as_ref()); 105 | 106 | Ok(()) 107 | } 108 | 109 | #[inline(always)] 110 | pub fn init_oracle(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 111 | let [quote, queue, payer, system_program, instructions_sysvar]: &[AccountInfo; 5] = 112 | accounts.try_into().map_err(|_| ProgramError::NotEnoughAccountKeys)?; 113 | 114 | let quote_data = Instructions::parse_ix_data_unverified(instructions_sysvar, 0) 115 | .map_err(|_| ProgramError::InvalidInstructionData)?; 116 | 117 | init_quote_account_if_needed( 118 | program_id, 119 | quote, 120 | queue, 121 | payer, 122 | system_program, 123 | "e_data, 124 | )?; 125 | 126 | Ok(()) 127 | } 128 | 129 | // Custom error codes 130 | // 0: InvalidQuoteAccount - Invalid quote account - not the canonical account for the contained feeds 131 | // 1: UnauthorizedSigner - Unauthorized signer - does not match stored signer 132 | -------------------------------------------------------------------------------- /solana/feeds/basic/scripts/README.md: -------------------------------------------------------------------------------- 1 | # Basic Oracle Integration Examples 2 | 3 | This directory contains simple, easy-to-understand examples for getting started with Switchboard On-Demand oracle integration using the new managed update system. 4 | 5 | ## Examples 6 | 7 | ### `managedUpdate.ts` - Complete Managed Update Flow 8 | A more detailed example demonstrating: 9 | - Canonical oracle account derivation 10 | - Managed update instruction creation 11 | - Custom program instructions for reading oracle data 12 | - Complete transaction flow with error handling 13 | 14 | **Perfect for**: Understanding the full flow, implementing in production apps (https://explorer.switchboardlabs.xyz/) 15 | 16 | ```bash 17 | npm run feeds:managed --feedId=0xef0d8b6fcd0104e3e75096912fc8e1e432893da4f18faedaacca7e5875da620f 18 | ``` 19 | 20 | ## Key Concepts 21 | 22 | ### Managed Updates 23 | The new managed update system uses the quote program to: 24 | - **Auto-detect network**: Automatically detects mainnet/devnet and selects appropriate queue 25 | - **Automatically handle oracle account creation**: No manual account management needed 26 | - **Verify Ed25519 signatures**: Verifies signatures from oracle operators 27 | - **Store verified quote data**: Stores data in canonical accounts 28 | - **Provide deterministic account derivation**: Same inputs always produce same accounts 29 | 30 | ### Canonical Oracle Accounts 31 | Oracle accounts are derived deterministically from feed hashes: 32 | ```typescript 33 | const [oracleAccount] = OracleQuote.getCanonicalPubkey(queue.pubkey, [feedId]); 34 | ``` 35 | 36 | This ensures: 37 | - Same feed IDs always produce the same oracle account 38 | - No manual account management needed 39 | - Automatic account creation if needed 40 | 41 | ### Network Auto-Detection 42 | The system automatically detects your network and selects the appropriate queue: 43 | ```typescript 44 | import { isMainnetConnection } from "@switchboard-xyz/on-demand"; 45 | 46 | // Auto-detect network and load appropriate queue 47 | const queue = await sb.Queue.loadDefault(program); 48 | const gateway = await queue.fetchGatewayFromCrossbar(crossbar); 49 | 50 | // Properly detect network using the official method 51 | const isMainnet = await isMainnetConnection(connection); 52 | console.log("Network detected:", isMainnet ? 'mainnet' : 'devnet'); 53 | console.log("Queue selected:", queue.pubkey.toBase58()); 54 | ``` 55 | 56 | ### Two-Instruction Pattern 57 | Managed updates use two instructions: 58 | 1. **Ed25519 Instruction**: Verifies oracle signatures 59 | 2. **Quote Program Instruction**: Stores verified data in oracle account 60 | 61 | Your program then reads from the oracle account: 62 | 3. **Your Program Instruction**: Consumes the verified oracle data 63 | 64 | ## Program Integration 65 | 66 | Your Anchor program should: 67 | 68 | 1. **Accept the oracle account** in your instruction accounts 69 | 2. **Verify the oracle data** using Switchboard's verification functions 70 | 3. **Extract feed values** for your business logic 71 | 72 | Example program instruction: 73 | ```rust 74 | use switchboard_on_demand::{ 75 | QuoteVerifier, SwitchboardQuote, default_queue, get_slot 76 | }; 77 | 78 | #[derive(Accounts)] 79 | pub struct UseOracleData<'info> { 80 | // The managed oracle account containing verified quote data 81 | // Validates it's the canonical account for the contained feeds 82 | #[account(address = quote_account.canonical_key(&default_queue()))] 83 | pub quote_account: InterfaceAccount<'info, SwitchboardQuote>, 84 | 85 | /// CHECK: Switchboard queue - validated in QuoteVerifier 86 | pub queue: AccountInfo<'info>, 87 | 88 | // Your program's accounts 89 | #[account(mut)] 90 | pub your_state: Account<'info, YourState>, 91 | 92 | // Required sysvars for verification 93 | pub clock: Sysvar<'info, Clock>, 94 | pub slothashes: Sysvar<'info, SlotHashes>, 95 | pub instructions: Sysvar<'info, Instructions>, 96 | } 97 | 98 | pub fn use_oracle_data(ctx: Context) -> Result<()> { 99 | // Verify the oracle data is recent and valid 100 | let quote = QuoteVerifier::new() 101 | .queue(&ctx.accounts.queue) 102 | .slothash_sysvar(&ctx.accounts.slothashes) 103 | .ix_sysvar(&ctx.accounts.instructions) 104 | .clock_slot(get_slot(&ctx.accounts.clock)) 105 | .max_age(20) // 20 slots max age 106 | .verify_account(&ctx.accounts.quote_account)?; 107 | 108 | // Extract feed values 109 | for feed in quote.feeds() { 110 | msg!("Feed: {}, Value: {}", feed.hex_id(), feed.value()); 111 | // Use the feed value in your business logic 112 | } 113 | 114 | Ok(()) 115 | } 116 | ``` 117 | 118 | ## Getting Started 119 | 120 | 1. **Run the example**: Start with `managedUpdate.ts` for learning 121 | 2. **Set your feed ID**: Use a real Switchboard feed hash 122 | 3. **See the integration**: Watch the oracle integration in action 123 | 4. **Modify for your needs**: Adapt the pattern to your program 124 | 125 | ## Finding Feed IDs 126 | 127 | Find feed IDs in the [Switchboard Explorer](https://ondemand.switchboard.xyz/) 128 | -------------------------------------------------------------------------------- /solana/prediction-market/programs/prediction-market/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use switchboard_on_demand::{SlotHashes, Instructions, QuoteVerifier}; 3 | use switchboard_protos::OracleFeed; 4 | use switchboard_protos::OracleJob; 5 | use switchboard_protos::oracle_job::oracle_job::{KalshiApiTask, JsonParseTask, Task}; 6 | use switchboard_protos::oracle_job::oracle_job::task; 7 | use switchboard_on_demand::QueueAccountData; 8 | use switchboard_on_demand::default_queue; 9 | use prost::Message; 10 | use solana_program::hash::hash; 11 | use serde_json::json; 12 | 13 | declare_id!("tmT6vW7rVTn6qa432sy5ScQsCUFqbQLvyHXHWuKjWzr"); 14 | 15 | /// Prediction Market Feed Verification Program 16 | /// 17 | /// Demonstrates how to: 18 | /// 1. Extract feed ID from oracle data 19 | /// 2. Recreate the oracle feed proto on-chain 20 | /// 3. Serialize and hash it 21 | /// 4. Verify the feed ID matches expected configuration 22 | #[program] 23 | pub mod prediction_market { 24 | use super::*; 25 | 26 | /// Verify that an oracle feed matches the expected Kalshi order configuration 27 | /// 28 | /// This instruction verifies the Ed25519 instruction at index 0 and extracts 29 | /// the feed ID, then recreates the feed proto to verify it matches. 30 | pub fn verify_kalshi_feed( 31 | ctx: Context, 32 | order_id: String, 33 | ) -> Result<()> { 34 | // Create the quote verifier from sysvars using builder pattern 35 | let mut verifier = QuoteVerifier::new(); 36 | verifier 37 | .queue(ctx.accounts.queue.as_ref()) 38 | .slothash_sysvar(ctx.accounts.slothashes.as_ref()) 39 | .ix_sysvar(ctx.accounts.instructions.as_ref()) 40 | .clock_slot(Clock::get()?.slot); 41 | 42 | // Verify the Ed25519 instruction at index 0 43 | let quote = verifier.verify_instruction_at(0).unwrap(); 44 | 45 | let feeds = quote.feeds(); 46 | require!(!feeds.is_empty(), ErrorCode::NoOracleFeeds); 47 | 48 | let feed = &feeds[0]; 49 | let actual_feed_id = feed.feed_id(); 50 | 51 | // Verify they match 52 | require!( 53 | *actual_feed_id == create_kalshi_feed_id(&order_id)?, 54 | ErrorCode::FeedMismatch 55 | ); 56 | 57 | msg!("✅ Feed ID verification successful!"); 58 | msg!("Feed ID: {}", faster_hex::hex_string(actual_feed_id)); 59 | msg!("Order ID: {}", order_id); 60 | msg!("Signed slot: {}", quote.slot()); 61 | 62 | Ok(()) 63 | } 64 | 65 | } 66 | 67 | /// Create Kalshi feed hash from order ID 68 | /// 69 | /// This recreates the feed proto structure and hashes it to derive the feed ID 70 | fn create_kalshi_feed_id(order_id: &str) -> Result<[u8; 32]> { 71 | 72 | // Build the Kalshi API URL 73 | let url = format!( 74 | "https://api.elections.kalshi.com/trade-api/v2/portfolio/orders/{}", 75 | order_id 76 | ); 77 | 78 | let feed = OracleFeed { 79 | name: Some("Kalshi Order Price".to_string()), 80 | jobs: vec![ 81 | OracleJob { 82 | tasks: vec![ 83 | Task { 84 | task: Some(task::Task::KalshiApiTask(KalshiApiTask { 85 | url: Some(url.clone()), 86 | api_key_id: Some("${KALSHI_API_KEY_ID}".to_string()), 87 | signature: Some("${KALSHI_SIGNATURE}".to_string()), 88 | timestamp: Some("${KALSHI_TIMESTAMP}".to_string()), 89 | ..Default::default() 90 | })), 91 | }, 92 | Task { 93 | task: Some(task::Task::JsonParseTask(JsonParseTask { 94 | path: Some("$.order.yes_price_dollars".to_string()), 95 | ..Default::default() 96 | })), 97 | }, 98 | ], 99 | weight: None, 100 | } 101 | ], 102 | min_job_responses: Some(1), 103 | min_oracle_samples: Some(1), 104 | max_job_range_pct: Some(0), 105 | }; 106 | 107 | // Encode as protobuf length-delimited bytes using prost::Message trait 108 | let bytes = OracleFeed::encode_length_delimited_to_vec(&feed); 109 | 110 | // Hash the protobuf bytes 111 | Ok(hash(&bytes).to_bytes()) 112 | } 113 | 114 | 115 | #[derive(Accounts)] 116 | pub struct VerifyFeed<'info> { 117 | #[account(address = default_queue())] 118 | pub queue: AccountLoader<'info, QueueAccountData>, 119 | pub slothashes: Sysvar<'info, SlotHashes>, 120 | pub instructions: Sysvar<'info, Instructions>, 121 | } 122 | 123 | #[error_code] 124 | pub enum ErrorCode { 125 | #[msg("No oracle feeds available")] 126 | NoOracleFeeds, 127 | 128 | #[msg("Feed hash mismatch - oracle feed does not match expected configuration")] 129 | FeedMismatch, 130 | 131 | #[msg("Invalid feed JSON")] 132 | InvalidFeedJson, 133 | 134 | #[msg("Failed to create quote verifier")] 135 | VerifierError, 136 | 137 | #[msg("Failed to verify Ed25519 instruction")] 138 | VerificationFailed, 139 | } 140 | -------------------------------------------------------------------------------- /sui/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.0.0] - 2025-10-27 4 | 5 | ### 🎉 Major Update: Quote Verifier Integration 6 | 7 | This release completely overhauls the Sui examples to use the new **Quote Verifier** approach introduced in the October 2025 Switchboard upgrade. 8 | 9 | ### ✨ Added 10 | 11 | #### Move Contract 12 | - **`sources/example.move`**: Complete Quote Consumer implementation with Quote Verifier 13 | - `QuoteConsumer` struct with embedded `QuoteVerifier` 14 | - `create_quote_consumer()`: Initialize with oracle queue verification 15 | - `update_price()`: Verify and update prices with security checks 16 | - `get_current_price()`: Read current verified price 17 | - `is_price_fresh()`: Check if price data is within max age 18 | - `calculate_collateral_ratio()`: Example DeFi logic 19 | - `should_liquidate()`: Example liquidation check 20 | - Full test suite with Move unit tests 21 | 22 | #### TypeScript Scripts 23 | - **`scripts/run.ts`**: Complete end-to-end example 24 | - Creates QuoteConsumer with Quote Verifier 25 | - Fetches oracle data from Crossbar 26 | - Verifies signatures on-chain 27 | - Updates price with validation 28 | - Displays events and results 29 | 30 | #### Configuration 31 | - **`Move.toml`**: Mainnet configuration with correct Switchboard address 32 | - **`Move.testnet.toml`**: Testnet configuration with correct Switchboard address 33 | - **`tsconfig.json`**: TypeScript configuration for scripts 34 | - **`package.json`**: Updated with new scripts and dependencies 35 | 36 | #### Documentation 37 | - **`README.md`**: Comprehensive guide with: 38 | - Quick start guide (30 seconds to deployment) 39 | - Expected output examples 40 | - Key concepts explanation 41 | - Security checks documentation 42 | - Multiple use case examples 43 | - Troubleshooting guide 44 | - Available feeds table 45 | - Move integration guide 46 | - TypeScript SDK usage 47 | 48 | - **`DEPLOYMENT.md`**: Step-by-step deployment guide 49 | - Network configuration 50 | - Build and test instructions 51 | - Deployment steps for testnet and mainnet 52 | - Troubleshooting common issues 53 | - Upgrade instructions 54 | - Network information 55 | 56 | ### 🔄 Changed 57 | 58 | #### Updated Examples 59 | - **`examples/quotes.ts`**: Enhanced with: 60 | - Network selection (mainnet/testnet) 61 | - Switchboard state fetching 62 | - Better error messages 63 | - Links to complete example 64 | - Improved documentation 65 | 66 | - **`examples/surge.ts`**: Maintained for WebSocket streaming 67 | - No changes (already up-to-date) 68 | 69 | #### Package Updates 70 | - Updated `@switchboard-xyz/sui-sdk` to `^0.1.9` 71 | - Removed local link dependencies 72 | - Added proper `@switchboard-xyz/on-demand` dependency 73 | - Added new npm scripts: 74 | - `npm run example`: Run complete Move integration example 75 | - `npm run build`: Build Move contract 76 | - `npm run build:testnet`: Build for testnet 77 | - `npm run test`: Run Move tests 78 | - `npm run deploy`: Deploy to mainnet 79 | - `npm run deploy:testnet`: Deploy to testnet 80 | 81 | ### 🔧 Fixed 82 | 83 | #### Addresses 84 | - **Mainnet**: Updated to `0xa81086572822d67a1559942f23481de9a60c7709c08defafbb1ca8dffc44e210` 85 | - **Testnet**: Updated to `0x28005599a66e977bff26aeb1905a02cda5272fd45bb16a5a9eb38e8659658cff` 86 | 87 | ### 🎯 Migration Guide 88 | 89 | If you're upgrading from the old aggregator-based approach: 90 | 91 | #### Old Approach (Aggregator) 92 | ```typescript 93 | // Create aggregator on-chain 94 | const aggregator = new Aggregator(sb, aggregatorId); 95 | await aggregator.fetchUpdateTx(tx); 96 | ``` 97 | 98 | ```move 99 | use switchboard::aggregator::Aggregator; 100 | let result = aggregator.current_result(); 101 | ``` 102 | 103 | #### New Approach (Quote Verifier) 104 | ```typescript 105 | // Create quote consumer with verifier 106 | const quotes = await Quote.fetchUpdateQuote(sb, tx, { 107 | feedHashes: [feedHash], 108 | numOracles: 3, 109 | }); 110 | 111 | tx.moveCall({ 112 | target: `${packageId}::example::update_price`, 113 | arguments: [consumer, quotes, feedHash, clock], 114 | }); 115 | ``` 116 | 117 | ```move 118 | use switchboard::quote::{QuoteVerifier, Quotes}; 119 | 120 | // Initialize with verifier 121 | let verifier = switchboard::quote::new_verifier(ctx, queue); 122 | 123 | // Verify and use quotes 124 | verifier.verify_quotes("es, clock); 125 | let quote = verifier.get_quote(feed_hash); 126 | let price = quote.result(); 127 | ``` 128 | 129 | ### 📊 Benefits of Quote Verifier 130 | 131 | 1. **Better Security**: Automatic queue verification and replay protection 132 | 2. **Simpler Code**: No need to manage aggregator state 133 | 3. **More Flexible**: Fetch multiple feeds in one transaction 134 | 4. **Cost Effective**: Pay only when you need data 135 | 5. **Fresher Data**: On-demand fetching ensures latest prices 136 | 137 | ### 🔗 Resources 138 | 139 | - [Switchboard Documentation](https://docs.switchboard.xyz) 140 | - [Sui SDK](https://www.npmjs.com/package/@switchboard-xyz/sui-sdk) 141 | - [Explorer](https://explorer.switchboard.xyz) 142 | - [Discord](https://discord.gg/switchboardxyz) 143 | 144 | --- 145 | 146 | ## [1.0.0] - Previous Release 147 | 148 | Initial release with aggregator-based approach (deprecated). 149 | 150 | -------------------------------------------------------------------------------- /evm/randomness/pancake-stacker/README.md: -------------------------------------------------------------------------------- 1 | # Switchboard Randomness Example (Simple) 2 | 3 | A simple coin flip demonstrating Switchboard's on-chain randomness on EVM chains using Foundry. No wagering - just flip a coin and get a verifiably random result (heads or tails). 4 | 5 | ## Prerequisites 6 | 7 | - [Foundry](https://book.getfoundry.sh/getting-started/installation) 8 | - [Bun](https://bun.sh/) 9 | 10 | --- 11 | 12 | ## Installation 13 | 14 | ### 1. Install Dependencies 15 | 16 | ```bash 17 | bun install 18 | ``` 19 | 20 | This installs: 21 | - `@switchboard-xyz/common` — Crossbar client for off-chain randomness resolution 22 | - `@switchboard-xyz/on-demand-solidity` — Solidity interfaces and libraries for Switchboard 23 | 24 | ### 2. Install Foundry Libraries 25 | 26 | ```bash 27 | forge install 28 | ``` 29 | 30 | --- 31 | 32 | ## Forge Remappings 33 | 34 | To import Switchboard interfaces in your Solidity contracts, configure forge remappings. 35 | 36 | `remappings.txt`: 37 | ```txt 38 | @switchboard-xyz/on-demand-solidity/=node_modules/@switchboard-xyz/on-demand-solidity 39 | ``` 40 | 41 | --- 42 | 43 | ## Integration Guide 44 | 45 | ### On-Chain: Smart Contract Integration 46 | 47 | The `CoinFlip.sol` contract demonstrates the core randomness flow: 48 | 49 | #### 1. Store the Switchboard Contract Reference 50 | 51 | ```solidity 52 | import { ISwitchboard } from '@switchboard-xyz/on-demand-solidity/interfaces/ISwitchboard.sol'; 53 | import { SwitchboardTypes } from '@switchboard-xyz/on-demand-solidity/libraries/SwitchboardTypes.sol'; 54 | 55 | contract CoinFlip { 56 | ISwitchboard public switchboard; 57 | 58 | constructor(address _switchboard) { 59 | switchboard = ISwitchboard(_switchboard); 60 | } 61 | } 62 | ``` 63 | 64 | #### 2. Request Randomness 65 | 66 | ```solidity 67 | function flipCoin() public { 68 | bytes32 randomnessId = keccak256(abi.encodePacked(msg.sender, block.timestamp)); 69 | switchboard.createRandomness(randomnessId, 1); 70 | 71 | flipRequests[msg.sender] = FlipRequest({ 72 | user: msg.sender, 73 | randomnessId: randomnessId, 74 | requestTimestamp: block.timestamp 75 | }); 76 | 77 | emit CoinFlipRequested(msg.sender, randomnessId); 78 | } 79 | ``` 80 | 81 | #### 3. Settle Randomness 82 | 83 | ```solidity 84 | function settleFlip(bytes calldata encodedRandomness) public returns (bool isHeads) { 85 | FlipRequest memory request = flipRequests[msg.sender]; 86 | 87 | // Settle the randomness on-chain 88 | switchboard.settleRandomness(encodedRandomness); 89 | 90 | // Get the randomness value 91 | SwitchboardTypes.Randomness memory randomness = switchboard.getRandomness(request.randomnessId); 92 | require(randomness.value != 0, "Randomness not resolved"); 93 | 94 | // Determine heads or tails: even = heads, odd = tails 95 | isHeads = uint256(randomness.value) % 2 == 0; 96 | 97 | emit CoinFlipSettled(request.user, isHeads, randomness.value); 98 | delete flipRequests[msg.sender]; 99 | } 100 | ``` 101 | 102 | --- 103 | 104 | ### Off-Chain: Resolving Randomness with Crossbar 105 | 106 | The `scripts/flip-the-coin.ts` script demonstrates the complete flow: 107 | 108 | ```typescript 109 | import { CrossbarClient } from "@switchboard-xyz/common"; 110 | 111 | const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz"); 112 | 113 | // 1. Request randomness on-chain 114 | await coinFlipContract.flipCoin(); 115 | 116 | // 2. Get flip data 117 | const randomnessId = await coinFlipContract.getFlipRandomnessId(wallet.address); 118 | const flipData = await coinFlipContract.getFlipData(wallet.address); 119 | 120 | // 3. Resolve via Crossbar 121 | const { encoded } = await crossbar.resolveEVMRandomness({ 122 | chainId: 143, 123 | randomnessId, 124 | timestamp: Number(flipData.rollTimestamp), 125 | minStalenessSeconds: Number(flipData.minSettlementDelay), 126 | oracle: flipData.oracle, 127 | }); 128 | 129 | // 4. Settle on-chain and get result 130 | const tx = await coinFlipContract.settleFlip(encoded); 131 | ``` 132 | 133 | --- 134 | 135 | ## Running the Example 136 | 137 | ### 1. Build the Contracts 138 | 139 | ```bash 140 | forge build 141 | ``` 142 | 143 | ### 2. Deploy the Contract 144 | 145 | ```bash 146 | forge script script/CoinFlip.s.sol:CoinFlipScript \ 147 | --rpc-url \ 148 | --private-key \ 149 | --broadcast 150 | ``` 151 | 152 | ### 3. Run the Coin Flip Script 153 | 154 | ```bash 155 | PRIVATE_KEY= \ 156 | COIN_FLIP_CONTRACT_ADDRESS= \ 157 | bun run scripts/flip-the-coin.ts 158 | ``` 159 | 160 | --- 161 | 162 | ## Web UI 163 | 164 | A browser-based UI is included for interacting with the deployed contract. 165 | 166 | ### Running the UI 167 | 168 | ```bash 169 | bun start 170 | ``` 171 | 172 | Then open [http://localhost:3000](http://localhost:3000). 173 | 174 | ### Using the UI 175 | 176 | 1. Enter the deployed contract address 177 | 2. Click "Connect Wallet" (MetaMask/Phantom) 178 | 3. Click "Flip the Coin" to request randomness 179 | 4. Click "Settle & Reveal" to resolve and see the result (heads or tails) 180 | 181 | --- 182 | 183 | ## Documentation 184 | 185 | - [Foundry Book](https://book.getfoundry.sh/) 186 | - [Switchboard Documentation](https://docs.switchboard.xyz/) 187 | -------------------------------------------------------------------------------- /solana/legacy/benchmarks/benchmarkCU.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Compute Unit Benchmark Tool 3 | * 4 | * This script measures the compute units (CU) consumed by Switchboard 5 | * oracle updates with varying configurations. It helps developers: 6 | * 7 | * - Optimize transaction compute budgets 8 | * - Understand scaling characteristics 9 | * - Plan for multi-feed implementations 10 | * - Estimate transaction costs 11 | * 12 | * The benchmark tests different combinations of: 13 | * - Number of feeds (1-5) 14 | * - Number of oracle signatures (1-5) 15 | * 16 | * Results show how compute usage scales with complexity, helping 17 | * you choose the optimal configuration for your use case. 18 | * 19 | * @example 20 | * ```bash 21 | * # Run the compute unit benchmark 22 | * bun run scripts/benchmarkCU.ts 23 | * 24 | * # Output example: 25 | * # Running test with 2 feed(s) and 3 signature(s)... 26 | * # Time to fetch update: 387ms 27 | * # Compute units used: 145,230 28 | * # Transaction sent: 5xY9Z... 29 | * ``` 30 | * 31 | * @module benchmarkCU 32 | */ 33 | 34 | import * as sb from "@switchboard-xyz/on-demand"; 35 | import { TX_CONFIG, sleep } from "../utils"; 36 | import { PublicKey } from "@solana/web3.js"; 37 | import * as anchor from "@coral-xyz/anchor"; 38 | import { AnchorUtils } from "@switchboard-xyz/on-demand"; 39 | import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; 40 | 41 | /** 42 | * Main benchmark execution 43 | * 44 | * Systematically tests different feed and signature combinations 45 | * to measure compute unit consumption. This helps understand: 46 | * 47 | * - Base CU cost for oracle verification 48 | * - Incremental cost per additional feed 49 | * - Incremental cost per additional signature 50 | * - Optimal configurations for different use cases 51 | * 52 | * @async 53 | * @function main 54 | */ 55 | (async function main() { 56 | // Initialize connection to Solana devnet 57 | const connection = new anchor.web3.Connection( 58 | "https://api.devnet.solana.com", 59 | "confirmed" 60 | ); 61 | 62 | // Load keypair - update this path to your keypair location 63 | const keypair = await AnchorUtils.initKeypairFromFile( 64 | "/Users/alexstewart/.config/solana/switchboard_work.json" 65 | ); 66 | const wallet = new NodeWallet(keypair); 67 | const provider = new anchor.AnchorProvider(connection, wallet); 68 | 69 | // Connect to Switchboard program on devnet 70 | const pid = sb.ON_DEMAND_DEVNET_PID; 71 | const program = await anchor.Program.at(pid, provider); 72 | 73 | /** 74 | * Test feed accounts on devnet 75 | * 76 | * These are pre-deployed example feeds used for benchmarking. 77 | * Replace with your own feed addresses if testing specific 78 | * configurations or job definitions. 79 | */ 80 | const feeds = [ 81 | new PublicKey("CmZjMhReYDmUZnwCLGhuE6Q5UyjUvAxSSEZNLAehMuY9"), 82 | new PublicKey("ceuXtwYhAd3hs9xFfcrZZq6byY1s9b1NfsPckVkVyuC"), 83 | new PublicKey("J9nrFWjDUeDVZ4BhhxsbQXWgLcLEgQyNBrCbwSADmJdr"), 84 | new PublicKey("F8oaENnmLEqyHoiX6kqYu7WbbMGvuoB15fXfWX6SXUdZ"), 85 | new PublicKey("69XisEUvgWYoKd9TiBWnqgZbFvW7jKTsAitjhjhZ19K"), 86 | ]; 87 | 88 | // Test matrix: vary both number of feeds and signatures 89 | for (let numFeeds = 1; numFeeds <= 5; numFeeds++) { 90 | for (let numSignatures = 1; numSignatures <= 5; numSignatures++) { 91 | console.log( 92 | `Running test with ${numFeeds} feed(s) and ${numSignatures} signature(s)...` 93 | ); 94 | 95 | // Select subset of feeds for this test iteration 96 | const selectedFeeds = feeds 97 | .slice(0, numFeeds) 98 | .map((pubkey) => new sb.PullFeed(program, pubkey)); 99 | 100 | // Measure oracle fetch time 101 | const timestart = Date.now(); 102 | try { 103 | // Fetch update instructions for multiple feeds 104 | // More signatures = higher security but more compute 105 | const [pullIx, luts] = await sb.PullFeed.fetchUpdateManyIx(program, { 106 | feeds: selectedFeeds, 107 | numSignatures: numSignatures, 108 | gateway: "https://api.switchboard.xyz", 109 | }); 110 | const timeEnd = Date.now(); 111 | console.log(`Time to fetch update: ${timeEnd - timestart}ms`); 112 | 113 | // Build transaction with fetched instructions 114 | const tx = await sb.asV0Tx({ 115 | connection, 116 | ixs: [pullIx[0], pullIx[1]], // Signature verification + update instructions 117 | signers: [keypair], 118 | computeUnitPrice: 200_000, // Priority fee for consistent inclusion 119 | computeUnitLimitMultiple: 1.3, // 30% buffer on compute units 120 | lookupTables: luts, // Address lookup tables for efficiency 121 | }); 122 | 123 | // Simulate to measure compute units 124 | const sim = await connection.simulateTransaction(tx, TX_CONFIG); 125 | const computeUnits = sim.value.unitsConsumed; 126 | console.log(`Compute units used: ${computeUnits}`); 127 | 128 | // Send actual transaction to verify real-world behavior 129 | const sentTx = await connection.sendTransaction(tx); 130 | console.log(`Transaction sent: ${sentTx}`); 131 | } catch (error) { 132 | console.error( 133 | `Error with ${numFeeds} feeds and ${numSignatures} signatures:`, 134 | error 135 | ); 136 | } 137 | 138 | // Delay between tests to avoid rate limits 139 | await sleep(3000); 140 | } 141 | } 142 | 143 | console.log("All test cases completed."); 144 | })(); 145 | -------------------------------------------------------------------------------- /solana/feeds/advanced/programs/advanced-oracle-example/src/utils.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | account_info::AccountInfo, 3 | msg, 4 | pubkey::Pubkey 5 | }; 6 | use switchboard_on_demand::OracleQuote; 7 | use pinocchio::instruction::AccountMeta; 8 | use pinocchio::sysvars::rent::Rent; 9 | use pinocchio::program_error::ProgramError; 10 | use pinocchio::program::invoke_signed; 11 | use pinocchio::instruction::Signer; 12 | use pinocchio::instruction::Instruction; 13 | use pinocchio::sysvars::Sysvar; 14 | use pinocchio::instruction::Seed; 15 | 16 | const SYSTEM_PROGRAM_ID: Pubkey = [0; 32]; 17 | 18 | pub const ORACLE_ACCOUNT_SIZE: usize = 8 + 32 + 1024; // discriminator (8) + queue (32) + data (1024) 19 | pub const STATE_ACCOUNT_SIZE: usize = 32; 20 | 21 | #[inline(always)] 22 | pub fn init_quote_account_if_needed( 23 | program_id: &Pubkey, 24 | oracle_account: &AccountInfo, 25 | queue_account: &AccountInfo, 26 | payer: &AccountInfo, 27 | system_program: &AccountInfo, 28 | oracle_quote: &OracleQuote, 29 | ) -> Result<(), ProgramError> { 30 | if oracle_account.lamports() != 0 { 31 | msg!("Oracle account already initialized"); 32 | return Ok(()); 33 | } 34 | // Check if system program is correct 35 | if system_program.key() != &SYSTEM_PROGRAM_ID { 36 | return Err(ProgramError::IncorrectProgramId); 37 | } 38 | 39 | // Verify the oracle account is the correct PDA derived from queue + feed IDs 40 | let feed_ids = oracle_quote.feed_ids(); 41 | let mut seeds_for_derivation: Vec<&[u8]> = Vec::with_capacity(feed_ids.len() + 1); 42 | seeds_for_derivation.push(queue_account.key().as_ref()); 43 | for feed_id in &feed_ids { 44 | seeds_for_derivation.push(feed_id.as_ref()); 45 | } 46 | let (canonical_address, bump) = pinocchio::pubkey::find_program_address(&seeds_for_derivation, program_id); 47 | if canonical_address != *oracle_account.key() { 48 | return Err(ProgramError::InvalidArgument); 49 | } 50 | 51 | // Prepare seeds for invoke_signed 52 | let mut seeds = Vec::with_capacity(feed_ids.len() + 2); 53 | seeds.push(Seed::from(queue_account.key().as_ref())); 54 | for feed_id in &feed_ids { 55 | seeds.push(Seed::from(feed_id.as_ref())); 56 | } 57 | let bump = [bump]; 58 | let bump_bytes = Seed::from(&bump); 59 | seeds.push(bump_bytes); 60 | 61 | // Calculate rent requirement 62 | let rent = Rent::get()?; 63 | let required_lamports = rent.minimum_balance(ORACLE_ACCOUNT_SIZE); 64 | 65 | // Create the system program instruction to create account 66 | let mut data = Vec::with_capacity(52); 67 | let create_account_ix = Instruction { 68 | program_id: &SYSTEM_PROGRAM_ID, 69 | accounts: &[ 70 | AccountMeta::new(payer.key(), true, true), 71 | AccountMeta::new(oracle_account.key(), true, true), 72 | ], 73 | data: { 74 | data.extend_from_slice(&0u32.to_le_bytes()); // CreateAccount instruction 75 | data.extend_from_slice(&required_lamports.to_le_bytes()); 76 | data.extend_from_slice(&(ORACLE_ACCOUNT_SIZE as u64).to_le_bytes()); 77 | data.extend_from_slice(program_id); 78 | &data 79 | }, 80 | }; 81 | 82 | // Make CPI to system program to create account 83 | invoke_signed( 84 | &create_account_ix, 85 | &[ 86 | payer, 87 | oracle_account, 88 | system_program, 89 | ], 90 | &[Signer::from(seeds.as_slice())] 91 | ) 92 | } 93 | 94 | #[inline(always)] 95 | pub fn init_state_account_if_needed( 96 | program_id: &Pubkey, 97 | state_account: &AccountInfo, 98 | payer: &AccountInfo, 99 | system_program: &AccountInfo, 100 | ) -> Result<(), ProgramError> { 101 | if state_account.lamports() != 0 { 102 | return Ok(()); 103 | } 104 | 105 | // Check if system program is correct 106 | if system_program.key() != &SYSTEM_PROGRAM_ID { 107 | return Err(ProgramError::IncorrectProgramId); 108 | } 109 | 110 | // Derive and verify the state account is the correct PDA 111 | let (expected_state_key, bump) = pinocchio::pubkey::find_program_address(&[b"state"], program_id); 112 | if state_account.key() != &expected_state_key { 113 | return Err(ProgramError::InvalidArgument); 114 | } 115 | 116 | // Calculate rent requirement 117 | let rent = Rent::get()?; 118 | let required_lamports = rent.minimum_balance(STATE_ACCOUNT_SIZE); 119 | 120 | // Create the system program instruction to create account 121 | let mut data = Vec::with_capacity(52); 122 | let create_account_ix = Instruction { 123 | program_id: &SYSTEM_PROGRAM_ID, 124 | accounts: &[ 125 | AccountMeta::new(payer.key(), true, true), 126 | AccountMeta::new(state_account.key(), true, true), 127 | ], 128 | data: { 129 | data.extend_from_slice(&0u32.to_le_bytes()); // CreateAccount instruction 130 | data.extend_from_slice(&required_lamports.to_le_bytes()); 131 | data.extend_from_slice(&(STATE_ACCOUNT_SIZE as u64).to_le_bytes()); 132 | data.extend_from_slice(program_id); 133 | &data 134 | }, 135 | }; 136 | 137 | // Make CPI to system program to create account 138 | invoke_signed( 139 | &create_account_ix, 140 | &[ 141 | payer, 142 | state_account, 143 | system_program, 144 | ], 145 | &[Signer::from(&[Seed::from(b"state"), Seed::from(&[bump])])], 146 | )?; 147 | 148 | // Account is created with zero-initialized data by default 149 | Ok(()) 150 | } 151 | -------------------------------------------------------------------------------- /solana/randomness/pancake-stacker/scripts/utils.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { 3 | Connection, 4 | Keypair, 5 | PublicKey, 6 | SystemProgram, 7 | Commitment, 8 | } from "@solana/web3.js"; 9 | import * as sb from "@switchboard-xyz/on-demand"; 10 | 11 | const COMMITMENT = "confirmed"; 12 | 13 | export async function myAnchorProgram( 14 | provider: anchor.Provider, 15 | keypath: string 16 | ): Promise { 17 | const myProgramKeypair = await sb.AnchorUtils.initKeypairFromFile(keypath); 18 | const pid = myProgramKeypair.publicKey; 19 | const idl = (await anchor.Program.fetchIdl(pid, provider))!; 20 | if (idl == null) { 21 | console.error("IDL not found for the program at", pid.toString()); 22 | process.exit(1); 23 | } 24 | if (idl?.address == undefined || idl?.address == null) { 25 | idl.address = pid.toString(); 26 | } 27 | const program = new anchor.Program(idl, provider); 28 | return program; 29 | } 30 | 31 | export async function loadSbProgram( 32 | provider: anchor.Provider 33 | ): Promise { 34 | const sbProgramId = await sb.getProgramId(provider.connection); 35 | const sbIdl = await anchor.Program.fetchIdl(sbProgramId, provider); 36 | const sbProgram = new anchor.Program(sbIdl!, provider); 37 | return sbProgram; 38 | } 39 | 40 | export async function initializePancakeStackerProgram( 41 | provider: anchor.Provider 42 | ): Promise { 43 | const myProgramPath = "../target/deploy/pancake_stacker-keypair.json"; 44 | const myProgram = await myAnchorProgram(provider, myProgramPath); 45 | console.log("Pancake Stacker program", myProgram.programId.toString()); 46 | return myProgram; 47 | } 48 | 49 | export async function setupQueue(program: anchor.Program): Promise { 50 | const queueAccount = await sb.getDefaultQueue( 51 | program.provider.connection.rpcEndpoint 52 | ); 53 | console.log("Queue account", queueAccount.pubkey.toString()); 54 | try { 55 | await queueAccount.loadData(); 56 | } catch (err) { 57 | console.error("Queue not found, ensure you are using devnet in your env"); 58 | process.exit(1); 59 | } 60 | return queueAccount.pubkey; 61 | } 62 | 63 | /** 64 | * Creates, simulates, sends, and confirms a transaction. 65 | */ 66 | export async function handleTransaction( 67 | sbProgram: anchor.Program, 68 | connection: Connection, 69 | ix: anchor.web3.TransactionInstruction[], 70 | keypair: Keypair, 71 | signers: Keypair[], 72 | txOpts: any 73 | ): Promise { 74 | const createTx = await sb.asV0Tx({ 75 | connection: sbProgram.provider.connection, 76 | ixs: ix, 77 | payer: keypair.publicKey, 78 | signers: signers, 79 | computeUnitPrice: 75_000, 80 | computeUnitLimitMultiple: 1.3, 81 | }); 82 | 83 | const sim = await connection.simulateTransaction(createTx, txOpts); 84 | const sig = await connection.sendTransaction(createTx, txOpts); 85 | await connection.confirmTransaction(sig, COMMITMENT); 86 | console.log(" Transaction Signature", sig); 87 | return sig; 88 | } 89 | 90 | export async function initializePlayer( 91 | myProgram: anchor.Program, 92 | playerStateAccount: [anchor.web3.PublicKey, number], 93 | keypair: Keypair, 94 | sbProgram: anchor.Program, 95 | connection: Connection 96 | ): Promise { 97 | const initIx = await myProgram.methods 98 | .initialize() 99 | .accounts({ 100 | playerState: playerStateAccount[0], 101 | user: keypair.publicKey, 102 | systemProgram: SystemProgram.programId, 103 | }) 104 | .instruction(); 105 | 106 | const txOpts = { 107 | commitment: "processed" as Commitment, 108 | skipPreflight: true, 109 | maxRetries: 0, 110 | }; 111 | await handleTransaction( 112 | sbProgram, 113 | connection, 114 | [initIx], 115 | keypair, 116 | [keypair], 117 | txOpts 118 | ); 119 | } 120 | 121 | /** 122 | * Creates the flip pancake instruction 123 | */ 124 | export async function createFlipPancakeInstruction( 125 | myProgram: anchor.Program, 126 | rngKpPublicKey: PublicKey, 127 | playerStateAccount: [anchor.web3.PublicKey, number], 128 | keypair: Keypair 129 | ): Promise { 130 | return await myProgram.methods 131 | .flipPancake(rngKpPublicKey) 132 | .accounts({ 133 | playerState: playerStateAccount[0], 134 | user: keypair.publicKey, 135 | randomnessAccountData: rngKpPublicKey, 136 | authority: keypair.publicKey, 137 | }) 138 | .instruction(); 139 | } 140 | 141 | /** 142 | * Creates the catch pancake instruction 143 | */ 144 | export async function createCatchPancakeInstruction( 145 | myProgram: anchor.Program, 146 | playerStateAccount: [anchor.web3.PublicKey, number], 147 | rngKpPublicKey: PublicKey, 148 | keypair: Keypair 149 | ): Promise { 150 | return await myProgram.methods 151 | .catchPancake() 152 | .accounts({ 153 | playerState: playerStateAccount[0], 154 | randomnessAccountData: rngKpPublicKey, 155 | user: keypair.publicKey, 156 | }) 157 | .instruction(); 158 | } 159 | 160 | /** 161 | * Fetch player stats from the program 162 | */ 163 | export async function getPlayerStats( 164 | myProgram: anchor.Program, 165 | playerStateAccount: PublicKey 166 | ): Promise<{ stackHeight: number; hasPendingFlip: boolean } | null> { 167 | try { 168 | const state = await myProgram.account.playerState.fetch(playerStateAccount); 169 | return { 170 | stackHeight: (state.stackHeight as anchor.BN).toNumber(), 171 | hasPendingFlip: state.hasPendingFlip as boolean, 172 | }; 173 | } catch (e) { 174 | return null; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /solana/feeds/basic/scripts/createManagedFeed.ts: -------------------------------------------------------------------------------- 1 | import * as sb from "@switchboard-xyz/on-demand"; 2 | import { CrossbarClient, OracleJob, OracleFeed } from "@switchboard-xyz/common"; 3 | import yargs from "yargs"; 4 | import { TX_CONFIG, buildBinanceJob, buildCoinbaseJob } from "../utils"; 5 | 6 | const argv = yargs(process.argv) 7 | .options({ 8 | name: { 9 | type: "string", 10 | default: "BTC/USD", 11 | description: "Name for the oracle feed", 12 | }, 13 | base: { 14 | type: "string", 15 | default: "BTC", 16 | description: "Base currency", 17 | }, 18 | quote: { 19 | type: "string", 20 | default: "USD", 21 | description: "Quote currency", 22 | }, 23 | }) 24 | .parseSync(); 25 | 26 | /** 27 | * Create and Test a Managed Oracle Feed 28 | * 29 | * This script demonstrates how to create a Switchboard managed feed using the modern 30 | * storeOracleFeed approach and fetch oracle-signed price updates. 31 | * 32 | * Usage: 33 | * ts-node feeds/createManagedFeed.ts [--name "BTC/USD"] [--base BTC] [--quote USD] 34 | * 35 | * What this script does: 36 | * 1. Defines oracle jobs that fetch price data from exchanges (e.g., Binance) 37 | * 2. Creates an OracleFeed object with aggregation configuration 38 | * 3. Stores the feed on IPFS via Crossbar API (returns a feed hash) 39 | * 4. Fetches oracle-signed updates using the feed hash 40 | * 5. Submits update to the canonical quote account on-chain 41 | * 6. Reads and displays the oracle price value from the quote account 42 | */ 43 | (async function main() { 44 | console.log("=== Creating Switchboard Managed Feed ==="); 45 | 46 | // Step 1: Load Solana environment 47 | const { program, keypair, connection } = await sb.AnchorUtils.loadEnv(); 48 | const queue = await sb.Queue.loadDefault(program!); 49 | const isMainnet = await sb.isMainnetConnection(connection); 50 | 51 | console.log("Network:", isMainnet ? "mainnet" : "devnet"); 52 | 53 | // Step 3: Define oracle jobs 54 | // Create jobs that fetch price data from multiple exchanges 55 | const jobs: OracleJob[] = [ 56 | buildBinanceJob(`${argv.base}USDT`), 57 | ]; 58 | 59 | // Step 4: Create OracleFeed object with configuration 60 | const oracleFeed = OracleFeed.fromObject({ 61 | name: `${argv.base}/${argv.quote} Price Feed`, 62 | jobs: jobs, 63 | minOracleSamples: 1, 64 | minJobResponses: 1, 65 | maxJobRangePct: 1, 66 | }); 67 | 68 | 69 | // Step 5: Store the feed on IPFS using storeOracleFeed 70 | const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz"); 71 | const { feedId, cid } = await crossbar.storeOracleFeed(oracleFeed); 72 | const feedHash = feedId.startsWith("0x") ? feedId.slice(2) : feedId; 73 | 74 | await new Promise(resolve => setTimeout(resolve, 3000)); 75 | 76 | // Step 6: Fetch oracle-signed update 77 | console.log("Fetching oracle update..."); 78 | let instructions; 79 | let oracleValue: number | null = null; 80 | 81 | try { 82 | // Fetch oracle update which contains the value 83 | const updateResponse = await queue.fetchManagedUpdateIxs( 84 | crossbar, 85 | [feedHash], 86 | { 87 | numSignatures: 1, 88 | variableOverrides: {}, 89 | instructionIdx: 0, 90 | payer: keypair.publicKey, 91 | } 92 | ); 93 | instructions = updateResponse; 94 | 95 | // Decode the Ed25519 instruction data to extract oracle value 96 | const ed25519Ix = updateResponse[0]; 97 | if (ed25519Ix && ed25519Ix.data) { 98 | try { 99 | const decodedQuote = sb.OracleQuote.decode(ed25519Ix.data); 100 | console.log("📊 Decoded Oracle Quote:"); 101 | console.log(` Recent Slot: ${decodedQuote.recentSlot.toString()}`); 102 | 103 | if (decodedQuote.feedInfos.length > 0) { 104 | const feed = decodedQuote.feedInfos[0]; 105 | console.log(` Feed Hash: ${feed.feedHash.toString("hex")}`); 106 | 107 | // Convert i128 to decimal (18 decimals precision) 108 | const DECIMALS = 18; 109 | const divisor = BigInt(10) ** BigInt(DECIMALS); 110 | const price = Number(feed.value) / Number(divisor); 111 | oracleValue = price; 112 | 113 | console.log(` Decoded Price: $${price.toLocaleString()}\n`); 114 | } 115 | } catch (decodeError: any) { 116 | console.warn("Could not decode oracle quote:", decodeError.message); 117 | } 118 | } 119 | } catch (error: any) { 120 | console.error("Failed to fetch oracle update:", error.message); 121 | if (error.response?.data) { 122 | console.error("Response:", error.response.data); 123 | } 124 | return; 125 | } 126 | 127 | // Step 7: Derive the canonical quote account 128 | const [quoteAccount] = sb.OracleQuote.getCanonicalPubkey(queue.pubkey, [feedHash]); 129 | 130 | // Step 8: Send update transaction 131 | const tx = await sb.asV0Tx({ 132 | connection, 133 | ixs: instructions, 134 | signers: [keypair], 135 | computeUnitPrice: 20_000, 136 | computeUnitLimitMultiple: 1.1, 137 | }); 138 | 139 | const sim = await connection.simulateTransaction(tx); 140 | if (sim.value.err) { 141 | console.error("Simulation failed:", sim.value.err); 142 | console.log("Logs:", sim.value.logs?.join("\n")); 143 | return; 144 | } 145 | 146 | console.log("Submitting oracle update..."); 147 | const sig = await connection.sendTransaction(tx, TX_CONFIG); 148 | try { 149 | await connection.confirmTransaction(sig, "confirmed"); 150 | console.log(`✓ Transaction: ${sig}\n`); 151 | } catch (error: any) { 152 | console.log(`⏳ Transaction sent: ${sig}`); 153 | } 154 | 155 | console.log(` Quote Account: ${quoteAccount.toBase58()}`); 156 | })(); 157 | -------------------------------------------------------------------------------- /common/rust-feed-creation/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use solana_sdk::pubkey::Pubkey; 3 | use std::str::FromStr; 4 | use switchboard_on_demand::client::CrossbarClient; 5 | 6 | #[tokio::main] 7 | async fn main() -> Result<()> { 8 | // Run the logic 9 | if let Err(e) = run_example().await { 10 | eprintln!("Error: {}", e); 11 | std::process::exit(1); 12 | } 13 | Ok(()) 14 | } 15 | 16 | async fn run_example() -> Result<()> { 17 | let crossbar_url = "https://crossbar.switchboard.xyz"; 18 | let client = CrossbarClient::new(crossbar_url, true); // verbose = true 19 | 20 | // Define the Queue (Mainnet default) 21 | let queue_pubkey = Pubkey::from_str("A43DyUGA7s8eXPxqEjJY6EBu1KKbNgfxF8h17VAHn13w")?; 22 | 23 | // Define the Feed Job (JSON) - Fetch BTC Price 24 | // Note: This is an IOracleJob wrapped in an OracleFeed (implicitly in v2) 25 | // For v2 store/simulate, we construct an OracleFeed-like structure. 26 | let feed_def = serde_json::json!({ 27 | "queue": queue_pubkey.to_string(), 28 | "jobs": [ 29 | { 30 | "tasks": [ 31 | { 32 | "httpTask": { 33 | "url": "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd" 34 | } 35 | }, 36 | { 37 | "jsonParseTask": { 38 | "path": "$.bitcoin.usd" 39 | } 40 | } 41 | ] 42 | } 43 | ] 44 | }); 45 | 46 | println!("Feed Definition: {}", serde_json::to_string_pretty(&feed_def)?); 47 | 48 | // Store the Feed Definition (v2) 49 | println!("\nStoring feed definition on Crossbar (v2)..."); 50 | let store_resp = client.store_oracle_feed(&feed_def).await?; 51 | 52 | println!("Store Response:"); 53 | println!(" Feed ID: {}", store_resp.feedId); 54 | 55 | // Simulate the Feed (v2) 56 | println!("\nSimulating feed (v2)..."); 57 | // We can simulate by feed hash (returned as feedId) 58 | let sim_resp = client.simulate_proto(&store_resp.feedId, false, Some("mainnet")).await?; 59 | 60 | println!("Feed Hash: {:?}", sim_resp.feedHash); 61 | println!("Results: {:?}", sim_resp.results); 62 | println!("Logs: {:?}", sim_resp.logs); 63 | 64 | Ok(()) 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | 71 | #[tokio::test] 72 | async fn test_feed_creation_and_simulation_v2() -> Result<()> { 73 | let crossbar_url = "https://crossbar.switchboard.xyz"; 74 | let client = CrossbarClient::new(crossbar_url, true); 75 | let queue_pubkey = Pubkey::from_str("A43DyUGA7s8eXPxqEjJY6EBu1KKbNgfxF8h17VAHn13w")?; 76 | 77 | // 1. Test HTTP + JSON Parse Task (BTC Price) using v2 methods 78 | let feed_def = serde_json::json!({ 79 | "queue": queue_pubkey.to_string(), 80 | "jobs": [ 81 | { 82 | "tasks": [ 83 | { 84 | "httpTask": { 85 | "url": "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd" 86 | } 87 | }, 88 | { 89 | "jsonParseTask": { 90 | "path": "$.bitcoin.usd" 91 | } 92 | } 93 | ] 94 | } 95 | ] 96 | }); 97 | 98 | println!("Testing V2 Feed Storage..."); 99 | let store_resp = client.store_oracle_feed(&feed_def).await?; 100 | println!("Stored Feed ID: {}", store_resp.feedId); 101 | assert!(!store_resp.feedId.is_empty(), "Feed ID should not be empty"); 102 | 103 | println!("Testing V2 Feed Simulation..."); 104 | let sim_resp = client.simulate_proto(&store_resp.feedId, false, Some("mainnet")).await?; 105 | assert!(!sim_resp.results.is_empty(), "Should return simulation results"); 106 | 107 | // Results are strings in v2 response 108 | let result_str = &sim_resp.results[0]; 109 | println!("HTTP Feed Result: {}", result_str); 110 | let result_dec = rust_decimal::Decimal::from_str(result_str)?; 111 | assert!(result_dec > rust_decimal::Decimal::ZERO, "BTC price should be positive"); 112 | 113 | // 2. Test Value Task (Static Value) using v2 114 | let value_feed = serde_json::json!({ 115 | "queue": queue_pubkey.to_string(), 116 | "jobs": [ 117 | { 118 | "tasks": [ 119 | { 120 | "valueTask": { 121 | "value": 123.45 122 | } 123 | } 124 | ] 125 | } 126 | ] 127 | }); 128 | 129 | println!("Testing V2 Value Feed Storage..."); 130 | let val_store_resp = client.store_oracle_feed(&value_feed).await?; 131 | println!("Stored Value Feed ID: {}", val_store_resp.feedId); 132 | 133 | println!("Testing V2 Value Feed Simulation..."); 134 | let val_sim_resp = client.simulate_proto(&val_store_resp.feedId, false, Some("mainnet")).await?; 135 | let val_result_str = &val_sim_resp.results[0]; 136 | 137 | println!("Value Feed Result: {}", val_result_str); 138 | let expected = rust_decimal::Decimal::from_str("123.45")?; 139 | let actual = rust_decimal::Decimal::from_str(val_result_str)?; 140 | assert_eq!(actual, expected, "Value task should return exact value"); 141 | 142 | Ok(()) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /solana/legacy/variable-overrides/README.md: -------------------------------------------------------------------------------- 1 | # Switchboard On-Demand: Variable Overrides Example 2 | 3 | This example demonstrates the use of Switchboard's On-Demand Feed using **Variable Overrides**. Variable overrides provide a simple, secure approach for credential management while maintaining feed verifiability. 4 | 5 | ## 🔧 Variable Overrides 6 | 7 | ### Advantages: 8 | - ✅ **Simpler Implementation**: No complex infrastructure required 9 | - ✅ **Environment Variable Based**: Standard credential management patterns 10 | - ✅ **Security Best Practices**: Only API keys use variable substitution 11 | - ✅ **Verifiable Feeds**: All data sources and extraction paths are hardcoded 12 | - ✅ **Standard Deployment**: Works with any deployment environment 13 | 14 | ### Security Model: 15 | Variable overrides follow the [security best practices](https://docs.switchboard.xyz/switchboard/readme/designing-feeds/data-feed-variable-overrides) from Switchboard documentation: 16 | 17 | - **✅ Safe Uses**: API keys (`${API_KEY}`) and authentication tokens (`${AUTH_TOKEN}`) 18 | - **❌ Dangerous Uses**: Base URLs, API versions, JSON paths, or any data selection logic 19 | 20 | ## 🚀 Getting Started 21 | 22 | ### Prerequisites 23 | Configure the `anchor.toml` file to point to your Solana wallet and cluster (Devnet, Mainnet, etc.). 24 | 25 | ### Build and Deploy 26 | ```bash 27 | anchor build 28 | ``` 29 | 30 | After building, note your program address and insert it in your program `lib.rs` file: 31 | ```rust 32 | declare_id!("[YOUR_PROGRAM_ADDRESS]"); 33 | ``` 34 | 35 | Rebuild and deploy: 36 | ```bash 37 | anchor build 38 | anchor deploy 39 | anchor idl init --filepath target/idl/sb_on_demand_solana.json YOUR_PROGRAM_ADDRESS 40 | ``` 41 | 42 | ### Environment Setup 43 | Create a `.env` file with your API credentials: 44 | 45 | ```bash 46 | # Required for weather API example 47 | OPEN_WEATHER_API_KEY=your_openweather_api_key_here 48 | 49 | # Optional for testing examples 50 | TEST_VALUE=12345 51 | AUTH_TOKEN=your_auth_token_here 52 | API_KEY=your_api_key_here 53 | ``` 54 | 55 | Get your OpenWeather API key from: https://openweathermap.org/api 56 | 57 | ### Install Dependencies 58 | ```bash 59 | bun install 60 | ``` 61 | 62 | ## 📊 Running Examples 63 | 64 | ### 1. Main Weather Feed Example 65 | ```bash 66 | bun run pull 67 | ``` 68 | This demonstrates the complete variable override workflow: 69 | - Uses `${OPEN_WEATHER_API_KEY}` for secure API key injection 70 | - Hardcoded weather API endpoint for Aspen, CO 71 | - Verifiable data extraction path (`$.main.temp`) 72 | 73 | ### 2. Simple Value Test 74 | ```bash 75 | TEST_VALUE=100 bun run test-simple 76 | ``` 77 | Demonstrates basic variable substitution with a simple value task. 78 | 79 | ### 3. Multi-Authentication Test 80 | ```bash 81 | AUTH_TOKEN=your_token API_KEY=your_key bun run test-multi-auth 82 | ``` 83 | Shows how to handle multiple authentication credentials securely. 84 | 85 | ## 🔒 Security Best Practices 86 | 87 | ### ✅ Recommended Patterns 88 | 89 | **API Key Only Pattern** (Most Secure): 90 | ```typescript 91 | { 92 | httpTask: { 93 | // ✅ Everything hardcoded except API key 94 | url: "https://api.openweathermap.org/data/2.5/weather?q=aspen,us&appid=${OPEN_WEATHER_API_KEY}&units=metric", 95 | method: "GET" 96 | } 97 | } 98 | ``` 99 | 100 | **Multiple Auth Headers**: 101 | ```typescript 102 | { 103 | httpTask: { 104 | url: "https://api.polygon.io/v2/last/trade/AAPL", // ✅ Hardcoded symbol 105 | headers: [ 106 | { key: "Authorization", value: "Bearer ${AUTH_TOKEN}" }, // ✅ Auth only 107 | { key: "X-API-Key", value: "${API_KEY}" } // ✅ Auth only 108 | ] 109 | } 110 | } 111 | ``` 112 | 113 | ### ❌ Patterns to Avoid 114 | 115 | **Never use variables for**: 116 | - Base URLs: `${BASE_URL}/api/data` ❌ 117 | - Data selection: `?symbol=${SYMBOL}` ❌ 118 | - JSON paths: `$.${DATA_KEY}.price` ❌ 119 | - Any parameter affecting data extraction ❌ 120 | 121 | ## 🎯 Key Benefits 122 | 123 | 1. **No Complex Infrastructure**: No secrets management system needed 124 | 2. **Standard Env Vars**: Uses familiar environment variable patterns 125 | 3. **Verifiable Feeds**: Consumers can verify exactly what data is fetched 126 | 4. **Secure by Design**: Only authentication uses variable substitution 127 | 5. **Easy Testing**: Simple environment variable configuration 128 | 129 | ## 📚 Related Documentation 130 | 131 | - [Variable Overrides Documentation](https://docs.switchboard.xyz/switchboard/readme/designing-feeds/data-feed-variable-overrides) 132 | - [Switchboard On-Demand Feeds](https://docs.switchboard.xyz/product-documentation/data-feeds) 133 | - [Oracle Job Configuration](https://protos.docs.switchboard.xyz/protos/OracleJob) 134 | 135 | ## 🔄 Implementation Notes 136 | 137 | Variable overrides use a simple, straightforward approach: 138 | 139 | 1. **No Special Infrastructure**: Standard environment variable patterns 140 | 2. **Simple Job Definitions**: Use `httpTask` with `${VARIABLE}` placeholders for API keys 141 | 3. **Environment Variables**: Use standard `.env` file for credential management 142 | 4. **Feed Fetching**: Add `variableOverrides` parameter to feed operations 143 | 144 | ## 💡 Example Use Cases 145 | 146 | - **Weather Data**: API key for OpenWeather, hardcoded location 147 | - **Stock Prices**: API key for financial data, hardcoded symbols 148 | - **Sports Scores**: API key for ESPN, hardcoded game IDs 149 | - **Social Media**: Bearer tokens for Twitter, hardcoded user IDs 150 | 151 | ## 🔍 Troubleshooting 152 | 153 | ### Missing Environment Variables 154 | ```bash 155 | Error: OPEN_WEATHER_API_KEY environment variable is required 156 | ``` 157 | **Solution**: Ensure your `.env` file contains all required API keys. 158 | 159 | ### Variable Name Mismatches 160 | Variables are case-sensitive and must match exactly between job definition and overrides: 161 | ```typescript 162 | // Job uses: "${API_KEY}" 163 | // Override must use: "API_KEY": "value" ✅ 164 | // NOT: "api_key": "value" ❌ 165 | ``` 166 | 167 | ### Testing API Endpoints 168 | ```bash 169 | # Test your API endpoint manually 170 | curl "https://api.openweathermap.org/data/2.5/weather?q=aspen,us&appid=YOUR_KEY&units=metric" 171 | ``` -------------------------------------------------------------------------------- /sui/DEPLOYMENT.md: -------------------------------------------------------------------------------- 1 | # Deployment Guide 2 | 3 | This guide walks you through deploying the Switchboard Oracle Quote Verifier example to Sui. 4 | 5 | ## Prerequisites 6 | 7 | 1. **Sui CLI installed** 8 | ```bash 9 | cargo install --locked --git https://github.com/MystenLabs/sui.git --branch mainnet sui 10 | ``` 11 | 12 | 2. **Sui wallet configured** 13 | ```bash 14 | sui client 15 | ``` 16 | 17 | 3. **Testnet or Mainnet SUI tokens** 18 | - Testnet: Use the [Sui Testnet Faucet](https://discord.com/channels/916379725201563759/971488439931392130) 19 | - Mainnet: Purchase SUI tokens 20 | 21 | ## Step 1: Configure Your Network 22 | 23 | ### For Testnet 24 | 25 | ```bash 26 | # Switch to testnet 27 | sui client switch --env testnet 28 | 29 | # Copy testnet configuration 30 | cp Move.testnet.toml Move.toml 31 | ``` 32 | 33 | ### For Mainnet 34 | 35 | ```bash 36 | # Switch to mainnet 37 | sui client switch --env mainnet 38 | 39 | # The default Move.toml is already configured for mainnet 40 | ``` 41 | 42 | ## Step 2: Build the Contract 43 | 44 | ```bash 45 | # Build the Move contract 46 | npm run build 47 | 48 | # Or use sui directly 49 | sui move build 50 | ``` 51 | 52 | Expected output: 53 | ``` 54 | INCLUDING DEPENDENCY Sui 55 | INCLUDING DEPENDENCY Switchboard 56 | BUILDING example 57 | ``` 58 | 59 | ## Step 3: Run Tests (Optional) 60 | 61 | ```bash 62 | npm run test 63 | 64 | # Or use sui directly 65 | sui move test 66 | ``` 67 | 68 | ## Step 4: Deploy the Contract 69 | 70 | ### Testnet Deployment 71 | 72 | ```bash 73 | npm run deploy:testnet 74 | 75 | # Or use sui directly 76 | sui client publish --gas-budget 100000000 77 | ``` 78 | 79 | ### Mainnet Deployment 80 | 81 | ```bash 82 | npm run deploy 83 | 84 | # Or use sui directly 85 | sui client publish --gas-budget 100000000 86 | ``` 87 | 88 | ## Step 5: Save Your Package ID 89 | 90 | After deployment, you'll see output like: 91 | 92 | ``` 93 | ╭─────────────────────────────────────────────────────────────────────────╮ 94 | │ Object Changes │ 95 | ├─────────────────────────────────────────────────────────────────────────┤ 96 | │ Created Objects: │ 97 | │ ┌── │ 98 | │ │ ObjectID: 0xABCD1234... │ 99 | │ │ Sender: 0x... │ 100 | │ │ Owner: Immutable │ 101 | │ │ ObjectType: 0xABCD1234::example::QuoteConsumer │ 102 | │ └── │ 103 | ╰─────────────────────────────────────────────────────────────────────────╯ 104 | ``` 105 | 106 | **Save the Package ID** (the long hex string starting with `0x`). You'll need it to run the examples. 107 | 108 | ## Step 6: Set Environment Variables 109 | 110 | Create a `.env` file or export variables: 111 | 112 | ```bash 113 | # Set your deployed package ID 114 | export EXAMPLE_PACKAGE_ID=0xYOUR_PACKAGE_ID_HERE 115 | 116 | # Optional: Configure other parameters 117 | export SUI_NETWORK=mainnet # or testnet 118 | export FEED_HASH=0x4cd1cad962425681af07b9254b7d804de3ca3446fbfd1371bb258d2c75059812 119 | export NUM_ORACLES=3 120 | ``` 121 | 122 | ## Step 7: Run the Example 123 | 124 | ```bash 125 | npm run example 126 | ``` 127 | 128 | ## Troubleshooting 129 | 130 | ### "Insufficient gas" 131 | 132 | Increase the gas budget: 133 | ```bash 134 | sui client publish --gas-budget 200000000 135 | ``` 136 | 137 | ### "Package dependency does not match its on-chain version" 138 | 139 | This can happen if the Switchboard package was upgraded. Update your dependencies: 140 | ```bash 141 | # Clean build directory 142 | rm -rf build/ 143 | 144 | # Rebuild 145 | npm run build 146 | ``` 147 | 148 | ### "Unable to resolve dependencies" 149 | 150 | Make sure you have internet access and can reach GitHub: 151 | ```bash 152 | # Test GitHub connectivity 153 | git ls-remote https://github.com/switchboard-xyz/sui.git 154 | 155 | # If that works, try building again 156 | npm run build 157 | ``` 158 | 159 | ### "Address already published" 160 | 161 | If you're trying to republish, you need to use a different address or upgrade the existing package. For testing, you can change the `example` address in Move.toml: 162 | 163 | ```toml 164 | [addresses] 165 | example = "0x0" # Change this to a different address 166 | ``` 167 | 168 | ## Upgrading Your Contract 169 | 170 | If you need to upgrade an already-deployed contract: 171 | 172 | 1. **Find your UpgradeCap object** 173 | ```bash 174 | sui client objects 175 | ``` 176 | 177 | 2. **Upgrade the package** 178 | ```bash 179 | sui client upgrade --upgrade-capability 0xYOUR_UPGRADE_CAP --gas-budget 100000000 180 | ``` 181 | 182 | ## Network Information 183 | 184 | ### Mainnet 185 | - **Switchboard Package**: `0xa81086572822d67a1559942f23481de9a60c7709c08defafbb1ca8dffc44e210` 186 | - **RPC URL**: `https://fullnode.mainnet.sui.io:443` 187 | - **Explorer**: `https://suiscan.xyz/mainnet` 188 | 189 | ### Testnet 190 | - **Switchboard Package**: `0x28005599a66e977bff26aeb1905a02cda5272fd45bb16a5a9eb38e8659658cff` 191 | - **RPC URL**: `https://fullnode.testnet.sui.io:443` 192 | - **Explorer**: `https://suiscan.xyz/testnet` 193 | 194 | ## Next Steps 195 | 196 | After successful deployment: 197 | 198 | 1. **Run the complete example** 199 | ```bash 200 | EXAMPLE_PACKAGE_ID=0xYOUR_PACKAGE_ID npm run example 201 | ``` 202 | 203 | 2. **Try the simple quote fetching** 204 | ```bash 205 | npm run quotes 206 | ``` 207 | 208 | 3. **Monitor real-time prices with Surge** 209 | ```bash 210 | export SURGE_API_KEY=your_api_key 211 | npm run surge 212 | ``` 213 | 214 | 4. **Integrate into your own project** 215 | - Copy the Move code from `sources/example.move` 216 | - Adapt the TypeScript code from `scripts/run.ts` 217 | - Customize for your specific use case 218 | 219 | ## Support 220 | 221 | If you encounter issues: 222 | - 📖 [Switchboard Documentation](https://docs.switchboard.xyz) 223 | - 💬 [Discord Community](https://discord.gg/switchboardxyz) 224 | - 🐛 [GitHub Issues](https://github.com/switchboard-xyz/sui/issues) 225 | 226 | --------------------------------------------------------------------------------