This repository demonstrates how to fetch off-chain data from Range’s Risk API using a Switchboard On-Demand Oracle, and verify the result fully on-chain inside a Solana program.
The example uses:
- 2 implementations for the on-chain program ( Pinocchio and Anchor)
- Switchboard On-Demand for fetching + signing off-chain data
- Range API for providing trusted risk scores
- TypeScript client for building and submitting the transaction
Note: The Anchor and Pinocchio programs are intentionally equivalent, showcasing how Switchboard On-Demand can be used in either framework.
-
Client side:
- Builds a Range Risk Score feed (HTTP + JSON parse + multiply + bound tasks)
- Requests Switchboard to fetch, sign, and return a quote for that feed
- Builds two instructions:
sigVerifyIx
— verifies Switchboard’s Ed25519 signaturesgetRiskScoreIx
— calls your on-chain program
-
On-chain program:
- Reconstructs the exact same feed definition
- Hashes it (
SHA-256(length-delimited-protobuf)
) ->feed_id
- Uses QuoteVerifier from switchboard-on-demand to verify:
- The quote signatures
- The quote’s feed_id matches the derived one
- The quote is fresh and valid
- Logs the verified risk score (0–100)
If the feed hash doesn’t match or the quote is stale, the transaction fails — ensuring tamper-proof, auditable oracle data.
- The Feed (client/sdk.ts)
The feed describes what the oracle should fetch and how to process it:
export function getRangeRiskScoreJob(): OracleJob {
return OracleJob.fromObject({
tasks: [
{
httpTask: {
url: 'https://api.range.org/v1/risk/address?address=<ADDRESS>&network=solana',
headers: [
{ key: 'accept', value: 'application/json' },
{ key: 'X-API-KEY', value: '${RANGE_API_KEY}' }, // resolved off-chain
],
},
},
{ jsonParseTask: { path: '$.riskScore' } },
{ multiplyTask: { scalar: 10 } }, // Scale 0–10 → 0–100
{ boundTask: { lowerBoundValue: '0', upperBoundValue: '100' } },
],
});
}
Switchboard hashes the serialized feed proto → feed_id.
- Request a Quote
getOracleJobSignature()
:
- Passes the feed directly to Switchboard’s
fetchQuoteIx
- Oracles execute the feed tasks, sign the result, and return a quote
- No need to pre-store the feed on Crossbar — it’s processed on-demand
Returns:
- sigVerifyIx: Ed25519 verification instruction (index 0)
- queue_account: the Switchboard queue to use
-
Order Ixs and send the transaction
-
Verify on chain
entrypoint.rs
reconstructs the feed, hashes it, and verifies:
- The quote signatures (using QuoteVerifier)
- The quote’s feed_id matches the on-chain derived hash
- If matched, logs the risk score
Install dependencies
cd anchor/client # or pinocchio/client
npm install
- Run the test
npm test
Expected output:
Using Payer: <YOUR_PAYER_PUBKEY>
[
{ value: '100000000000000000000', feed_hash: '...', num_oracles: 1 }
]
FeedId: 0xee6ffd4f...
Fetched RiskScore Via Oracle. Tx: 2ZQs9febdjypEd5C43FkmyvTQiJe41Xcb44dBzx9DsQ1X9sSvniS76c6Tft8QiM2kf5jEbN6sgyCcN5ZZqEhuqC8
✔ initializes the Oracle and call the Oracle Program
On the explorer logs you should see:
Program log: Risk Score 100
-
Deterministic feed hash: Any feed change (URL, headers, or tasks) changes the hash → rejects stale or spoofed quotes.
-
On-chain signature verification: The first transaction instruction must be the Ed25519 verification ix. The program checks it using Switchboard’s QuoteVerifier.
-
Freshness enforcement: Uses Clock and SlotHashes sysvars to reject old quotes.
-
Secret safety: ${RANGE_API_KEY} is injected only off-chain via variableOverrides; never stored or sent on-chain.
To integrate Range + Switchboard in your own Solana program:
- Define your feed in TypeScript (HTTP → Parse → Transform → Bound)
- Request a quote using fetchQuoteIx
- In your program, reconstruct the same feed and hash it
- Use QuoteVerifier to check signatures + freshness
- Compare the feed IDs and trust only matching results