SPEC 01 · DOC-2026-07-11

Architecture

Pegd is a Solana-native issuance framework — a prototype vault that a protocol brings collateral to, from which it emits a calibrated stablecoin. Every layer is named, every account is sized, every hop from the issuer wizard to the on-chain mint is documented. This page is the system map: eight workspace packages, two deployed apps, one Anchor program, and the oracle plus attestor mesh that keeps the peg honest.

System overview

The framework separates the immutable core — an Anchor program that owns issuance, redemption, and attestation state — from the composable adapters that surround it. The core is small enough to audit line by line. Everything else is upgradeable, replaceable, or entirely optional.

IMMUTABLE

Core

The pegd_issuance Anchor program. Twelve instructions, seven account structs, one declare_id. Custody, mint authority, and circuit-breaker logic all live inside this single crate.

UPGRADEABLE

Adapters

Attestor nodes, the FastAPI indexer, the Next.js control room, the SDK, and the CLI. Any of them can be swapped without touching the on-chain code — the program never trusts a TypeScript byte.

Packages and apps

The monorepo contains eight packages and two apps, each pinned to a single responsibility. Package boundaries are enforced by the pnpm workspace graph — the SDK cannot import from the CLI, the CLI cannot reach into the Anchor crate.

PackageLanguageResponsibility
anchor-programRustIssuance core, PoR commit, breaker
issuance-coreTypeScriptClient-side vault math and quoting
por-attestorTypeScriptSigned reserve statements to chain
yield-engineTypeScriptToken-2022 interest-bearing wiring
risk-moduleTypeScriptBreaker thresholds, liquidation queue
compliance-hooksRustOptional SPL transfer hook program
sdk-tsTypeScriptPublic @pegd/sdk facade
cliTypeScriptpegd-cli npm binary
apps/webNext.js 14Vault scene, Inspector, Studio, Registry
apps/serviceFastAPIIndexer, dashboard API, badge renderer

Anchor program structure

The on-chain surface is one Anchor crate under packages/anchor-program/programs/pegd_issuance. Every PDA seed prefix is short, unambiguous, and encoded in the program constants. The IDL that ships to the SDK is generated from the exact same crate — there is no manual JSON to drift.

PDA seeds

// Program-derived addresses. All seeds are lowercase ASCII.
config       = pda([b"config"], program_id);
registry     = pda([b"registry"], program_id);
stable       = pda([b"stable", authority.as_ref()], program_id);
vault        = pda([b"vault", stable_mint.as_ref()], program_id);
attestation  = pda([b"attestation", stable_mint.as_ref()], program_id);
risk         = pda([b"risk", stable_mint.as_ref()], program_id);
oracle       = pda([b"oracle", stable_mint.as_ref()], program_id);

// Collateral vault token account = associated_token_address(
//   owner = vault PDA,
//   mint  = collateral_mint,
// );

Instruction handlers

Twelve handlers cover the full lifecycle. Each handler is a thin shell — it constructs a Context around the accounts it needs and delegates to a pure function in state/ so unit tests can drive the math without account plumbing.

HandlerEffect
initialize_configCreates Config singleton, sets admin
register_stableAllocates mint with Token-2022 extensions
deposit_collateralMoves collateral into the vault ATA
mint_stableEmits units after ratio and cap checks
burn_stableRetires supply, unlocks redemption
redeem_collateralBurns then returns proportional collateral
commit_attestationVerifies M-of-N signers, writes ratio
update_priceReads Pyth PriceUpdateV2, rejects stale
pause_issuanceManual breaker by admin authority
resume_issuanceClears breaker after fresh attestation
set_risk_paramsUpdates RiskParams within config lock
set_yield_rateCPI update_rate on interest-bearing mint

Account layout

Seven Anchor account structs. Every struct carries a _reserved: [u8; 32] pad so future fields can be added without a migration.

#[account]
pub struct VaultState {
    pub stable_mint:          Pubkey, // the Token-2022 mint we own authority for
    pub collateral_vault:     Pubkey, // ATA(vault, collateral_mint)
    pub collateral_mint:      Pubkey, // SPL or Token-2022
    pub total_collateral:     u64,    // raw units at the collateral mint's decimals
    pub total_minted:         u64,    // raw units at stable_mint decimals
    pub collateral_ratio_bps: u32,    // u32 avoids the 655.35% overflow on u16
    pub last_update_ts:       i64,
    pub is_paused:            bool,
    pub bump:                 u8,
    pub _reserved:            [u8; 32],
}

impl VaultState {
    pub const SPACE: usize = 8 + 32 * 3 + 8 * 3 + 4 + 1 + 1 + 32;
}

Oracle architecture

Two feeds. One median. Three signers.

The program consumes two independent data planes: a price feed from Pyth for the peg-currency exchange rate, and a set of reserve attestations from a bonded attestor mesh. Neither feed is ever trusted alone. Prices carry a confidence interval; reserves carry an M-of-N quorum. The median ratio wins.

  • ·update_price reads a Pyth PriceUpdateV2 account through the pull-based Solana receiver, rejecting on staleness beyond max_staleness_s or on conf / price wider than max_confidence_bps.
  • ·commit_attestation introspects the Instructions sysvar and verifies M preceding Ed25519 SigVerify instructions — Solana precompile, not an in-program check.
  • ·The custody attestor is a separate signer role — a natural person or key custodian who signs a periodic solvency statement for the off-chain reserve portion (attested-fiat and RWA modes only).

Proof-of-Reserve pipeline

The PoR path is a four-stage pipeline. Every stage is idempotent so a re-run at any point produces the same on-chain commit for the same inputs. This is what makes attestation replayable during audits.

// Stage 1 — collector: read raw balances and prices.
const collateralUnits = await connection.getTokenAccountBalance(vaultAta);
const priceUpdate     = await hermes.getLatestPriceUpdates([feedId]);

// Stage 2 — aggregator: value the vault in the peg currency.
const collateralUsd = collateralUnits.value.uiAmount *
                      priceUpdate.parsed[0].price.price *
                      Math.pow(10, priceUpdate.parsed[0].price.expo);
const totalSupplyUsd = totalSupplyUi * pegPrice;
const ratioBps       = Math.floor(collateralUsd / totalSupplyUsd * 10_000);

// Stage 3 — signer set: each attestor signs the same 186-byte body.
const body      = borsh.serialize(attestationBodySchema, snapshot);
const signature = ed25519.sign(body, attestorSecret);

// Stage 4 — on-chain commit: bundle M ed25519 verifies + program call.
const tx = new Transaction()
  .add(Ed25519Program.createInstructionWithPublicKey({ publicKey, message: body, signature }))
  .add(program.methods.commitAttestation(payload).instruction());
await sendAndConfirm(connection, tx, signers);

Token-2022 extension flow

Every stablecoin registered through Pegd is a Token-2022 mint. Two extensions matter: interest-bearing (always attached, rate may be zero) and transfer hook (only when the issuer opts in to compliance hooks). Extension order is fixed at allocation time and cannot be added later.

use anchor_spl::token_2022::spl_token_2022::{
    extension::ExtensionType, state::Mint,
};

let mut extensions = vec![ExtensionType::InterestBearingConfig];
if params.has_compliance_hook {
    extensions.push(ExtensionType::TransferHook);
}

let mint_len = ExtensionType::try_calculate_account_len::<Mint>(&extensions)?;
system_program::create_account(
    cpi_ctx,
    Rent::get()?.minimum_balance(mint_len),
    mint_len as u64,
    &spl_token_2022::ID,
)?;

// Interest-bearing must initialize BEFORE initialize_mint2.
anchor_spl::token_2022_extensions::interest_bearing_mint_initialize(
    ctx.accounts.into_interest_bearing_ctx(),
    Some(config_pda),
    params.yield_rate_bps,
)?;

if params.has_compliance_hook {
    initialize_transfer_hook(&mint, compliance_hook_program_id)?;
}

// Only after all extensions are attached.
initialize_mint2(cpi_ctx, params.decimals, &vault_pda, None)?;

Architecture principles

  • ·Separation of concerns. The Anchor program never speaks to Pyth Hermes over HTTP — that is the attestor's job. Attestors never mint — that is the issuer's job. Every boundary is enforced by process, key, or PDA.
  • ·Immutable core, upgradeable adapters. Program upgrades pass through a Squads multisig and a 48-hour timelock. Attestor binaries, indexer schemas, and web routes ship on their own cadence.
  • ·Cold-white minimalism. The control room shows the ratio, the reserve value, the breaker state, and nothing else. Every additional widget is a chance to misread the peg.
  • ·Same-origin fetches. The browser only ever calls /api/*. Route Handlers hold the Helius key and the Railway URL server-side. CORS is a non-event.

Deployment topology

EDGE + NODE

Vercel

pegd-web serves the vault scene, static docs, and the Route Handler proxy layer at /api/*. Helius keys live here as server-only env vars.

FASTAPI + PG + REDIS

Railway

pegd-service hosts the indexer, reserve time-series, badge renderer, and the WebSocket fan-out. Postgres and Redis are managed add-ons.

MAINNET-BETA

Solana

pegd_issuance plus the optional compliance_hook program. Upgrades gated by Squads multisig with a 48-hour timelock.

Environment matrix

VariableSurfaceNotes
NEXT_PUBLIC_SOLANA_RPCweb (client)Public mainnet-beta only. Never Helius.
HELIUS_RPC_URLweb (server), serviceKeyed. Consumed by Route Handlers only.
CORS_ORIGINSserviceFour exact origins. No wildcard.
ANCHOR_PROGRAM_IDservice, web (server)Set once after mainnet deploy.
DATABASE_URLserviceManaged Postgres injected by Railway.

Read next

Back to Docs