Full lesson
molpha-solana-program
What it does
From the project README:
The on-chain half of the Molpha oracle network on Solana: a versioned node registry, USDC subscriptions, feeds updated by threshold Schnorr attestations, and epoch-based node rewards. The attestation format is shared with Molpha's EVM contracts, so one coalition signature verifies on both.
- Original
- molpha/molpha-solana-program
- SuperStack fork
- Blockchains/molpha-solana-program @
31d65f0 - Stars
- 0
- Last push (original)
- 2026-10-27
- Languages
- Rust, TypeScript, JavaScript, Python
- Detected stack
- @solana/kitAnchorweb3.js
- Categories
- Other
- Licence
- Apache-2.0
Architecture
147 source files. By extension: .rs ×103, .ts ×35, .js ×7, .py ×2.
| Top-level directory | Source files |
|---|---|
programs | 67 |
molpha-tests | 36 |
cli | 35 |
scripts | 9 |
On-chain programs: programs/molpha
Dapp: devnet deploy pending
⏳ Devnet deploy pending — goes live when the devnet wallet is topped up. This fork's own dapp is not deployed yet, so there is no program address or transaction to show. It is queued in dapp-queue.txt; once devnet SOL is available it is deployed, exercised with real devnet transactions, and this section switches to the live dapp at /dapps/molpha-solana-program/. Until then, the lesson and step-by-step guide below are complete, and you can run it locally with the commands below.
Key code, explained
Excerpts chosen automatically where the code uses the most Solana concepts, pinned to the fork's commit.
programs/molpha/src/instructions/gateway/deactivate_gateway.rs L13–36 @ 31d65f0 · Apache-2.0 · original gateway.status = GatewayStatus::Deactivated; emit!(GatewayDeactivated { gateway: gateway.key(), authority: gateway.authority, deactivated_at: clock.unix_timestamp, }); Ok(())} #[derive(Accounts)]pub struct DeactivateGateway<'info> { #[account(mut)] pub authority: Signer<'info>, #[account( has_one = authority, seeds = [ProtocolConfig::SEED_PREFIX], bump = protocol_config.bump, )] pub protocol_config: Account<'info, ProtocolConfig>, #[account( mut,- Line 15
emit!(GatewayDeactivated {: Emits an Anchor event that clients can parse from the logs. - Line 18
deactivated_at: clock.unix_timestamp,: Reads on-chain time from the Clock sysvar. - Line 26
pub authority: Signer<'info>,: Requires this account to have signed the transaction. - Line 29
has_one = authority,: Checks that a stored pubkey field matches the passed account. - Line 30
seeds = [ProtocolConfig::SEED_PREFIX],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 31
bump = protocol_config.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
programs/molpha/src/instructions/revenue/withdraw_protocol_reserve.rs L25–48 @ 31d65f0 · Apache-2.0 · original &[&ctx.accounts.protocol_config.seeds()], ), amount, )?; emit!(ProtocolReserveWithdrawn { destination: ctx.accounts.destination.key(), amount, withdrawn_at: Clock::get()?.unix_timestamp, }); Ok(())} #[derive(Accounts)]pub struct WithdrawProtocolReserve<'info> { pub authority: Signer<'info>, #[account( mut, has_one = authority, seeds = [ProtocolConfig::SEED_PREFIX], bump = protocol_config.bump, )]- Line 30
emit!(ProtocolReserveWithdrawn {: Emits an Anchor event that clients can parse from the logs. - Line 33
withdrawn_at: Clock::get()?.unix_timestamp,: Reads on-chain time from the Clock sysvar. - Line 41
pub authority: Signer<'info>,: Requires this account to have signed the transaction. - Line 45
has_one = authority,: Checks that a stored pubkey field matches the passed account. - Line 46
seeds = [ProtocolConfig::SEED_PREFIX],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 47
bump = protocol_config.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
programs/molpha/src/instructions/admin/update_registry_policy.rs L25–48 @ 31d65f0 · Apache-2.0 · original redundancy_buffer, )?; emit!(RegistryUpdated { new_version: ctx.accounts.registry_state.current_version, node_count, redundancy_buffer, reason: RegistryVersionReason::PolicyChanged, updated_at: Clock::get()?.unix_timestamp, }); Ok(())} #[derive(Accounts)]pub struct UpdateRegistryPolicy<'info> { #[account(mut)] pub authority: Signer<'info>, #[account( has_one = authority, seeds = [ProtocolConfig::SEED_PREFIX], bump )] pub protocol_config: Account<'info, ProtocolConfig>,- Line 28
emit!(RegistryUpdated {: Emits an Anchor event that clients can parse from the logs. - Line 33
updated_at: Clock::get()?.unix_timestamp,: Reads on-chain time from the Clock sysvar. - Line 41
pub authority: Signer<'info>,: Requires this account to have signed the transaction. - Line 44
has_one = authority,: Checks that a stored pubkey field matches the passed account. - Line 45
seeds = [ProtocolConfig::SEED_PREFIX],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 46
bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
programs/molpha/src/instructions/slash_offence.rs L73–96 @ 31d65f0 · Apache-2.0 · original node_count, redundancy_buffer, reason: RegistryVersionReason::NodePunished, updated_at: clock.unix_timestamp, }); // Cancelled reward credits stop being a claim on the treasury. config.reward_liability = config .reward_liability .checked_sub(batch.slashed_rewards) .ok_or(error!(MolphaError::RewardLiabilityUnderflow))?; // Sweep the forfeited principal out of the deposit vault in one transfer, whatever the size // of the guilty set. The amount comes directly from the guilty nodes' locked balances. if batch.slashed_locked > 0 { require!( ctx.accounts.node_deposit_vault.amount >= batch.slashed_locked, MolphaError::DepositVaultUnderfunded ); token::transfer( CpiContext::new_with_signer( ctx.accounts.token_program.key(), Transfer {- Line 76
updated_at: clock.unix_timestamp,: Reads on-chain time from the Clock sysvar. - Line 82
.checked_sub(batch.slashed_rewards): Uses checked arithmetic so overflow fails instead of wrapping. - Line 88
require!(: An explicit validation that aborts the instruction with an error. - Line 93
token::transfer(: An SPL token transfer through the Token Program. - Line 94
CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
How to run, build and test
These commands come from the repo's own manifests (package.json scripts, Anchor.toml, Cargo, go.mod, Makefile). Check the README for prerequisites and environment variables.
| Directory | Command | Runs |
|---|---|---|
. | npm run build | yarn build:cli && yarn build:molpha |
. | npm run test | anchor build -p molpha && cargo test --workspace |
. | npm run lint | prettier --check "cli/**/*.{js,ts}" "scripts/**/*.{js,mjs,ts}" |
. | anchor build | compile programs and generate IDLs |
. | anchor test | local validator + test suite |
. | anchor run test | cargo test --workspace |
. | cargo build | Rust workspace build |
. | cargo test | Rust tests |
What you could build from it
- Fork the SuperStack copy and extend it within the Other gaps listed on the idea page.
- Learn the pattern first: Program Derived Addresses (PDAs)
- Learn the pattern first: Cross-Program Invocations (CPIs)