Documentation Index
Fetch the complete documentation index at: https://docs.molpha.io/llms.txt
Use this file to discover all available pages before exploring further.
Introduction
Molpha is a decentralized oracle network that provides reliable and secure data feeds for various assets. This guide will walk you through the process of integrating with Molpha oracles on the Solana blockchain, allowing your programs to access real-time data.
- Program ID:
moLfaMTgKNysiLevoQZk8igxektjJj4LtxSiUdfKHY3
- Serialization Format: Borsh
- Network: Solana Mainnet/Devnet
Key Concepts
The Molpha oracle program is built around a few key accounts:
The Feed Account
The Feed account is the primary account you will interact with. It stores all the information about a specific data feed, including:
name_hash: A 32-byte unique identifier for the feed (hash of the feed name).
authority: The public key of the account that can manage the feed.
feed_type: The type of feed (Public or Personal).
latest_answer: The most recent data point published to the feed.
answer_history: A vector containing the history of recent answers.
subscription_due_time: The Unix timestamp (i64) when the feed subscription expires.
The structure of the Feed account matches the Molpha program’s IDL:
use anchor_lang::prelude::*;
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub struct Feed {
pub name_hash: [u8; 32],
pub authority: Pubkey,
pub feed_type: FeedType,
pub job_id: [u8; 32],
pub data_source: Pubkey,
pub min_signatures_threshold: u8,
pub frequency: u64,
pub ipfs_cid: String,
pub latest_answer: Answer,
pub answer_history: Vec<Answer>,
pub history_idx: u64,
pub subscription_due_time: i64,
pub price_per_second_scaled: u64,
pub created_at: i64,
pub bump: u8,
}
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub enum FeedType {
Public,
Personal,
}
The Answer Struct
The Answer struct contains the actual data value and the timestamp when it was recorded.
#[derive(Clone, AnchorSerialize, AnchorDeserialize, Copy, Default)]
pub struct Answer {
pub value: [u8; 32],
pub timestamp: i64,
}
The value is a 32-byte array, which can represent different data types depending on the feed. For example, for a price feed, it could be a scaled integer encoded in the bytes. You’ll need to decode this based on your feed’s metadata or documentation.
On-chain Integration
To use Molpha oracle data in your Solana program, you need to:
- Define the
Feed and Answer structs matching the Molpha program’s structure.
- Pass the
Feed account to your instruction with proper owner verification.
- Access the
latest_answer to get the current value.
- Verify the feed subscription is still active.
1. Define the Account Structures
First, you need to define the account structures that match the Molpha program. These should be defined in your program:
use anchor_lang::prelude::*;
// Molpha program ID
pub const MOLPHA_PROGRAM_ID: Pubkey = pubkey!("moLfaMTgKNysiLevoQZk8igxektjJj4LtxSiUdfKHY3");
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub struct Answer {
pub value: [u8; 32],
pub timestamp: i64,
}
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub enum FeedType {
Public,
Personal,
}
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub struct Feed {
pub name_hash: [u8; 32],
pub authority: Pubkey,
pub feed_type: FeedType,
pub job_id: [u8; 32],
pub data_source: Pubkey,
pub min_signatures_threshold: u8,
pub frequency: u64,
pub ipfs_cid: String,
pub latest_answer: Answer,
pub answer_history: Vec<Answer>,
pub history_idx: u64,
pub subscription_due_time: i64,
pub price_per_second_scaled: u64,
pub created_at: i64,
pub bump: u8,
}
2. Define the Instruction Context
Define the accounts required for your instruction. Important: You must verify that the feed account is owned by the Molpha program using the owner constraint.
#[derive(Accounts)]
pub struct ReadPrice<'info> {
#[account(
owner = MOLPHA_PROGRAM_ID @ ErrorCode::InvalidFeedOwner
)]
pub molpha_feed: Account<'info, Feed>,
}
#[error_code]
pub enum ErrorCode {
#[msg("Feed account is not owned by Molpha program")]
InvalidFeedOwner,
#[msg("Feed subscription has expired")]
SubscriptionExpired,
}
3. Implement the Instruction Logic
In your instruction logic, you can access the deserialized molpha_feed account and read its latest_answer. Always verify the subscription is active before using the data.
pub fn read_price(ctx: Context<ReadPrice>) -> Result<()> {
let feed = &ctx.accounts.molpha_feed;
// Verify subscription is still active
let clock = Clock::get()?;
require!(
feed.subscription_due_time > clock.unix_timestamp,
ErrorCode::SubscriptionExpired
);
// Access the latest answer
let latest_answer = feed.latest_answer;
let price_value = latest_answer.value;
let timestamp = latest_answer.timestamp;
msg!("Latest price value: {:?}", price_value);
msg!("Timestamp: {}", timestamp);
// Decode the value based on your feed's format
// For example, if it's a u64 price:
// let price = u64::from_le_bytes(price_value[0..8].try_into().unwrap());
// Your program logic here...
Ok(())
}
4. Complete Example
Here is a complete example of a Solana program that reads from a Molpha oracle:
use anchor_lang::prelude::*;
declare_id!("YourProgramId111111111111111111111111111111");
// Molpha program ID
pub const MOLPHA_PROGRAM_ID: Pubkey = pubkey!("moLfaMTgKNysiLevoQZk8igxektjJj4LtxSiUdfKHY3");
#[program]
pub mod my_dapp {
use super::*;
pub fn read_price(ctx: Context<ReadPrice>) -> Result<()> {
let feed = &ctx.accounts.molpha_feed;
// Verify subscription is still active
let clock = Clock::get()?;
require!(
feed.subscription_due_time > clock.unix_timestamp,
ErrorCode::SubscriptionExpired
);
// Access the latest answer
let latest_answer = feed.latest_answer;
let price_value = latest_answer.value;
let timestamp = latest_answer.timestamp;
msg!("Latest answer timestamp: {}", timestamp);
msg!("Value bytes: {:?}", price_value);
// Example: Decode as u64 (adjust based on your feed's format)
let price = u64::from_le_bytes(
price_value[0..8]
.try_into()
.map_err(|_| ErrorCode::InvalidPriceFormat)?
);
msg!("Decoded price: {}", price);
// Your program logic here...
Ok(())
}
}
#[derive(Accounts)]
pub struct ReadPrice<'info> {
#[account(
owner = MOLPHA_PROGRAM_ID @ ErrorCode::InvalidFeedOwner
)]
pub molpha_feed: Account<'info, Feed>,
}
// Account structures (must match Molpha program)
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub struct Answer {
pub value: [u8; 32],
pub timestamp: i64,
}
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub enum FeedType {
Public,
Personal,
}
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub struct Feed {
pub name_hash: [u8; 32],
pub authority: Pubkey,
pub feed_type: FeedType,
pub job_id: [u8; 32],
pub data_source: Pubkey,
pub min_signatures_threshold: u8,
pub frequency: u64,
pub ipfs_cid: String,
pub latest_answer: Answer,
pub answer_history: Vec<Answer>,
pub history_idx: u64,
pub subscription_due_time: i64,
pub price_per_second_scaled: u64,
pub created_at: i64,
pub bump: u8,
}
#[error_code]
pub enum ErrorCode {
#[msg("Feed account is not owned by Molpha program")]
InvalidFeedOwner,
#[msg("Feed subscription has expired")]
SubscriptionExpired,
#[msg("Invalid price format")]
InvalidPriceFormat,
}
Important Notes
- Owner Verification: Always use the
owner constraint to verify the feed account belongs to the Molpha program. This prevents malicious accounts from being passed as feeds.
- Subscription Check: Always verify that
subscription_due_time > current_timestamp before using feed data.
- Value Decoding: The
value field is a 32-byte array. You need to decode it according to your feed’s data format (e.g., u64, i64, f64, etc.).
- Account Discriminator: Anchor automatically handles the 8-byte discriminator when using
Account<'info, Feed>, so you don’t need to manually skip it.
Off-chain Usage with TypeScript
You can also fetch and use Molpha oracle data from off-chain applications using TypeScript. You’ll need the Molpha program IDL to properly deserialize the account data.
Using Anchor SDK
import { Connection, PublicKey } from '@solana/web3.js';
import { Program, AnchorProvider, Wallet } from '@coral-xyz/anchor';
import { IDL as MolphaIdl, Molpha } from './molpha_idl'; // Import your Molpha IDL
async function getOraclePrice(feedAddress: string) {
// Connect to Solana network
const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
// Create a dummy wallet for read-only operations
// For write operations, use a real wallet
const wallet = {
publicKey: PublicKey.default,
signTransaction: async (tx: any) => tx,
signAllTransactions: async (txs: any[]) => txs,
} as Wallet;
const provider = new AnchorProvider(connection, wallet, {
commitment: 'confirmed',
});
// Molpha program ID
const molphaProgramId = new PublicKey('moLfaMTgKNysiLevoQZk8igxektjJj4LtxSiUdfKHY3');
const program = new Program<Molpha>(MolphaIdl, molphaProgramId, provider);
// Fetch the feed account
const feedPubkey = new PublicKey(feedAddress);
const feedAccount = await program.account.feed.fetch(feedPubkey);
// Access feed data
const latestAnswer = feedAccount.latestAnswer;
const valueBytes = Buffer.from(latestAnswer.value);
const timestamp = new Date(Number(latestAnswer.timestamp) * 1000);
// Check subscription status
const currentTime = Math.floor(Date.now() / 1000);
const isActive = Number(feedAccount.subscriptionDueTime) > currentTime;
console.log('Feed Authority:', feedAccount.authority.toString());
console.log('Latest Answer Value (hex):', valueBytes.toString('hex'));
console.log('Timestamp:', timestamp.toISOString());
console.log('Subscription Active:', isActive);
console.log('Subscription Due Time:', new Date(Number(feedAccount.subscriptionDueTime) * 1000).toISOString());
// Example: Decode as u64 (little-endian)
if (valueBytes.length >= 8) {
const price = valueBytes.readBigUInt64LE(0);
console.log('Decoded Price (u64):', price.toString());
}
return {
feed: feedAccount,
latestAnswer,
valueBytes,
timestamp,
isActive,
};
}
// Usage
getOraclePrice('YourFeedAddressHere...')
.then(result => console.log('Feed data:', result))
.catch(error => console.error('Error:', error));
Using Raw Account Data (Without Anchor)
If you don’t have the Anchor IDL, you can deserialize the account data manually using Borsh:
import { Connection, PublicKey } from '@solana/web3.js';
import * as borsh from '@coral-xyz/borsh';
// Define the schema matching the Feed struct
const FeedSchema = borsh.struct([
borsh.array(borsh.u8(), 32, 'nameHash'),
borsh.publicKey('authority'),
borsh.u8('feedType'), // 0 = Public, 1 = Personal
borsh.array(borsh.u8(), 32, 'jobId'),
borsh.publicKey('dataSource'),
borsh.u8('minSignaturesThreshold'),
borsh.u64('frequency'),
borsh.string('ipfsCid'),
borsh.struct([
borsh.array(borsh.u8(), 32, 'value'),
borsh.i64('timestamp'),
], 'latestAnswer'),
// Note: answer_history is a Vec, which requires length prefix
// This is a simplified version - you may need to handle Vec deserialization
]);
async function getFeedDataRaw(feedAddress: string) {
const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
const feedPubkey = new PublicKey(feedAddress);
const accountInfo = await connection.getAccountInfo(feedPubkey);
if (!accountInfo) {
throw new Error('Feed account not found');
}
// Skip the 8-byte discriminator (Anchor account discriminator)
const data = accountInfo.data.slice(8);
// Deserialize using Borsh
const feed = FeedSchema.decode(data);
console.log('Feed Authority:', feed.authority.toString());
console.log('Latest Answer Value:', Buffer.from(feed.latestAnswer.value).toString('hex'));
console.log('Timestamp:', new Date(Number(feed.latestAnswer.timestamp) * 1000).toISOString());
return feed;
}
Finding Feed Addresses
Feed addresses are PDAs derived from:
- Seed:
"feed"
- Authority: The feed’s authority public key
- Name hash: The 32-byte hash of the feed name
- Feed type: The feed type enum value
You can derive the feed address using Anchor’s findProgramAddress or by querying the Molpha indexer/API if available.
Best Practices
- Always Verify Ownership: Use the
owner constraint to ensure the feed account belongs to the Molpha program.
- Check Subscription Status: Always verify that
subscription_due_time > current_timestamp before using feed data.
- Handle Stale Data: Check the
timestamp of the latest answer to ensure the data is recent enough for your use case.
- Error Handling: Implement proper error handling for cases where the feed account doesn’t exist or subscription has expired.
- Value Decoding: Understand your feed’s data format. The 32-byte
value array may represent different types (u64, i64, f64, etc.) depending on the feed configuration.
- Account Validation: Consider validating other feed properties like
min_signatures_threshold if your use case requires a minimum number of signatures.
Common Issues
Account Discriminator Mismatch
If you get an “AccountDiscriminatorMismatch” error, ensure:
- Your
Feed struct exactly matches the Molpha program’s structure
- You’re using
Account<'info, Feed> with the owner constraint
- The account data hasn’t been corrupted
Subscription Expired
Always check subscription_due_time before using feed data. If the subscription has expired, the feed may not be updated anymore.
Value Decoding
The value field is a 32-byte array. You need to know the encoding format:
- For u64:
u64::from_le_bytes(value[0..8].try_into().unwrap())
- For i64:
i64::from_le_bytes(value[0..8].try_into().unwrap())
- For f64: Use appropriate floating-point decoding
- Check the feed’s IPFS metadata or documentation for the exact format
Next Steps
Resources