Skip to main content
A job is the unit of oracle work: a definition of what to fetch, how to parse it, and what quorum is required. Jobs are created on Solana under an active subscription; only a hash of the API configuration is stored on-chain — large payloads and secrets stay off-chain.

Job identity

jobId = keccak256("MOLPHA_JOB_V1" || owner || apiConfigHash)
  • owner — the 32-byte Solana public key of the job creator
  • apiConfigHash — 32-byte keccak commitment to the off-chain API configuration
The SDK exports the derivation directly:
import { deriveJobId, deriveApiConfigHash } from "@molpha-oracle/sdk";

const jobId = deriveJobId(owner.toBytes(), deriveApiConfigHash(apiConfig));
Because jobId is fully determined by (owner, apiConfigHash), anyone who knows those two inputs can compute the job’s feed address with no on-chain lookups — the Feed PDA is derived from jobId. Changing the API configuration changes the hash, which produces a different jobId: a job is immutably bound to its config.

On-chain Job account

#[account]
pub struct Job {
    pub job_id: [u8; 32],
    pub owner: Pubkey,
    pub delegates: [Pubkey; 5],     // optional addresses allowed to manage the job
    pub delegate_count: u8,
    pub api_config_hash: [u8; 32],  // commitment to the off-chain API configuration
    pub decimals: u8,               // fixed-point scale for `value`
    pub signatures_required: u8,    // quorum requested for this job
    pub created_at: i64,
    pub bump: u8,
}
Constraints enforced at creation:
  • The subscription must be active (valid_until in the future)
  • signatures_required <= plan.max_signers
  • job_count < plan.max_jobs
  • job_id must equal keccak256("MOLPHA_JOB_V1" || owner || api_config_hash)

Delegates

add_delegate / remove_delegate let the job owner authorize up to 5 additional addresses to manage the job. This is a management convenience only — it does not affect how the feed is read.

Values and decoding

The oracle value is a 32-byte big-endian word — the same encoding in the cross-chain signed message, in the Solana Feed account, and in the EVM/Starknet calldata structs. Interpretation is defined by the application that created the job. For numeric feeds the word is a big-endian two’s-complement integer (int256) or unsigned integer (uint256), scaled by the job’s decimals (stored on the Job account):
humanValue = int(value) / 10 ** decimals
import { BN } from "bn.js";

function decodeNumeric(value: Uint8Array, decimals: number): number {
  // Unsigned interpretation; use a signed decode if your feed can be negative.
  const raw = new BN(Buffer.from(value), "be");
  return raw.toNumber() / 10 ** decimals;
}
For hashes, packed structs, or booleans, treat value as raw bytes and decode according to your job’s documented schema.
Endianness on Solana: the oracle value field is big-endian to match the cross-chain signed-message encoding, but the account’s Borsh scalar fields (canonical_timestamp, last_updated_slot, registry_version) are little-endian. Decode accordingly when reading the Feed account by offset.

Choosing APIs that work

Independent nodes must converge on a byte-identical value to co-sign it. Every selected node fetches the API independently, and they must all arrive at the same 32-byte value. When designing a job:
  • Prefer settled, finalized data (final scores, closed-day aggregates, immutable records) over live-drifting values.
  • Avoid responses containing per-request server timestamps, request IDs, or unstable ordering.
  • Pin the parse path (responseParser) to exactly the field you need, so unrelated response changes don’t break convergence.