Integration Guide
This guide provides detailed instructions for integrating the Morpher Oracle into your Radix DApp. Follow these steps to start using real-time price data in your smart contracts.
Integration Steps
- Subscribe to the Oracle NFT (buy a Subscription to get signed prices)
- Build a backend that signs price requests
- Validate signed price responses in your smart contracts
1. Subscribe to Oracle
First, you need to purchase a subscription NFT to access the Oracle services:
- Visit the appropriate Subscription Page:
- Mainnet Subscription for production use
- Stokenet Subscription for testing
- Connect your Radix wallet
- Purchase a subscription NFT
- Note your NFT ID for future reference
2. Generate Cryptographic Keys
Generate a BLS12-381 key pair for signing Oracle requests:
import * as ed from '@noble/ed25519';
import { bls12_381 as bls } from '@noble/curves/bls12-381';
// Generate a secure random private key
const privateKey = ed.utils.randomPrivateKey();
const hexPrivateKey = ed.etc.bytesToHex(privateKey);
// Derive the public key
function getPublicKey(privateKey) {
return Array.from(
bls.getPublicKey(privateKey),
byte => byte.toString(16).padStart(2, '0')
).join('');
}
const publicKey = getPublicKey(privateKey);
console.log('Private Key:', hexPrivateKey);
console.log('Public Key:', publicKey);
⚠️ IMPORTANT: Store your private key securely! It should never be exposed to users or included in client-side code.
3. Enroll Your Public Key
Enroll your public key in your subscription NFT:
- Return to the appropriate Subscription Page:
- Mainnet Subscription for production use
- Stokenet Subscription for testing
- Connect your wallet containing the subscription NFT
- Enter your public key in the enrollment form
- Submit the transaction to update your NFT
4. Set Up Your DApp Backend
Create a backend service to handle Oracle requests:
import express from 'express';
import { bytesToHex } from '@noble/curves/abstract/utils';
import { bls12_381 as bls } from '@noble/curves/bls12-381';
const app = express();
const port = process.env.PORT || 3001;
// Use custom DST for BLS signatures
const htfEthereum = { DST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_' };
// Types for Oracle messages
type PricePoint = {
marketId: string;
price: number;
nonce: string;
dataTimestamp: number;
oracleTimestamp: number;
marketStatusTimestamp: number;
marketStatus: string;
}
type PriceMessage = {
data: PricePoint[];
signature?: string;
}
type OracleRequestMessage = {
marketId: string;
publicKeyBLS: string;
nftId: string;
signature: string;
}
// Helper function to get public key from private key
function getPublicKey(privateKey) {
return Array.from(
bls.getPublicKey(privateKey),
byte => byte.toString(16).padStart(2, '0')
).join('');
}
// Helper function to convert message to string format for signing
function oracleRequestMsgToString(msg: OracleRequestMessage): string {
return `${msg.marketId}##${msg.publicKeyBLS}##${msg.nftId}`;
}
// Endpoint to get signed price data
app.get("/example/getPrice", async (req, res) => {
try {
const marketId = req.query.marketId || "GATEIO:XRD_USDT";
const nftId = process.env.ORACLE_NFT_ID;
const privateKey = process.env.PK_DAPP;
if (!nftId || !privateKey) {
return res.status(500).json({ error: "Missing environment variables" });
}
// Create the request message
let oracleRequestMsg = {
marketId: marketId.toString(),
publicKeyBLS: getPublicKey(privateKey),
nftId,
signature: ""
};
// Convert message to string format for signing
const msgString = oracleRequestMsgToString(oracleRequestMsg);
const msgHex = Buffer.from(msgString, 'utf8').toString('hex');
// Sign the message
const signature = bytesToHex(await bls.sign(msgHex, privateKey, htfEthereum));
oracleRequestMsg.signature = signature;
// Request price data from Oracle backend (v2 API)
const oracleUrl = `${process.env.ORACLE_BACKEND_URL}/v2/price/${marketId}/${oracleRequestMsg.publicKeyBLS}/${oracleRequestMsg.nftId}/${oracleRequestMsg.signature}`;
const response = await fetch(oracleUrl);
if (!response.ok) {
throw new Error(`Oracle API error: ${response.status}`);
}
const priceData = await response.json();
res.json(priceData);
} catch (error) {
console.error('Error fetching price:', error);
res.status(500).json({ error: error.message });
}
});
// Endpoint to get the DApp's public key
app.get("/example/getPublicKey", async (req, res) => {
const privateKey = process.env.PK_DAPP;
res.json({ publicKey: getPublicKey(privateKey) });
});
app.listen(port, () => {
console.log(`DApp backend running on port ${port}`);
});
5. Implement Frontend Integration
Add Oracle data fetching to your frontend:
// api.ts
export async function getPrice(
marketId: string = 'GATEIO:XRD_USDT',
apiEndpoint: string = 'https://your-dapp-backend.com'
): Promise<{ data: PriceMessage, status: number }> {
try {
const response = await fetch(`${apiEndpoint}/api/getPrice?marketId=${marketId}`);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
return { data, status: response.status };
} catch (error) {
console.error('Error fetching price:', error);
throw error;
}
}
// Component using the price data
function PriceDisplay() {
const [priceData, setPriceData] = useState<PriceMessage | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchPrice = async () => {
setIsLoading(true);
try {
const { data } = await getPrice();
setPriceData(data);
setError(null);
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};
// Use the price data to create a transaction manifest
const createTransaction = () => {
if (!priceData || !priceData.signature) return;
// Create transaction manifest with the signed price data
// ...
};
return (
<div>
<button onClick={fetchPrice} disabled={isLoading}>
{isLoading ? 'Loading...' : 'Get Latest Price'}
</button>
{error && <div className="error">{error}</div>}
{priceData && (
<div>
<h3>Price Data</h3>
<p>Market: {priceData.data[0].marketId}</p>
<p>Price: {priceData.data[0].price}</p>
<p>Timestamp: {new Date(priceData.data[0].dataTimestamp * 1000).toLocaleString()}</p>
<button onClick={createTransaction}>
Use This Price
</button>
</div>
)}
</div>
);
}
6. Create Smart Contract with Oracle Integration
Implement a smart contract that uses the Oracle:
use scrypto::prelude::*;
#[blueprint]
mod my_dapp {
struct MyDapp {
oracle_component: Global<MorpherOracle>,
price_lifetime: u64,
}
impl MyDapp {
pub fn new(oracle_address: Global<MorpherOracle>) -> Global<MyDapp> {
Self {
oracle_component: oracle_address,
price_lifetime: 120, // 2 minutes
}
.instantiate()
.globalize()
}
pub fn execute_with_price_data(&mut self, message: String, signature: String) {
// Call the Oracle component to verify the signature and get the price data
// This is the key part - your smart contract calls the Oracle component directly
let price_message = self.oracle_component.check_price_input(message, signature);
// Perform additional validation on the verified price data
self.validate_price_freshness(&price_message);
// Use the verified price data in your business logic
let price = price_message.price;
info!("Verified price: {}", price);
// Your business logic here...
}
fn validate_price_freshness(&self, price_message: &PriceMessage) {
// Verify the price is fresh (not too old)
assert!(
price_message.oracle_timestamp + self.price_lifetime >= get_time(),
"This price is out of date!"
);
// Verify the market data is consistent with market status
assert!(
price_message.data_timestamp + 60 >= price_message.market_status_timestamp,
"This price is out of date!"
);
}
}
}
Testing Your Integration
- Local Testing: Use the Stokenet test network for initial development
- Subscription Testing: Verify your subscription NFT is properly configured
- Request Flow Testing: Test the full flow from frontend to backend to Oracle
- Transaction Testing: Verify that transactions with Oracle data execute correctly
Common Issues and Solutions
- Signature Verification Failures: Ensure you’re using the correct key format and signing algorithm
- NFT Ownership Issues: Verify the NFT is in the correct account and properly enrolled
- Timestamp Errors: Check that your system clocks are synchronized
- API Connection Problems: Verify network connectivity and API endpoint URLs
For a working example of Oracle integration, check out our Gumball Demo: