Full lesson
afterbell
Protection for tokenized stocks until the next open, on the chain where you hold them, hedged on Hyperliquid.
What it does
From the project README:
[](https://github.com/Osiyomeoh/afterbell/actions/workflows/ci.yml) [](https://github.com/Osiyomeoh/afterbell/actions/workflows/audit.yml)
- Original
- Osiyomeoh/afterbell
- SuperStack fork
- Blockchains/afterbell @
d1b583e - Stars
- 0
- Last push (original)
- 2026-10-12
- Languages
- TypeScript, Rust, Solidity, CSS, JavaScript
- Detected stack
- @solana/kitAnchorNext.jsweb3.js
- Categories
- DeFi
- Licence
- MIT
Architecture
318 source files. By extension: .ts ×226, .tsx ×50, .rs ×24, .sol ×18.
| Top-level directory | Source files |
|---|---|
app | 144 |
solana | 53 |
evm | 33 |
hedger | 30 |
quoter | 14 |
sdk | 12 |
research | 11 |
pricing | 9 |
On-chain programs: solana/programs/afterbell-vault
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/afterbell/. 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.
solana/programs/afterbell-vault/src/instructions/sizer.rs L49–71 @ d1b583e · MIT · original s.max_shares = b.max_shares; s.min_start_bps = b.min_start_bps; s.max_depth_bps = b.max_depth_bps; s.bump = ctx.bumps.sizer; emit!(SizerSet { order, sizer: b.sizer, max_shares: b.max_shares, min_start_bps: b.min_start_bps, max_depth_bps: b.max_depth_bps }); Ok(())} #[derive(Accounts)]pub struct RemoveSizer<'info> { #[account(mut)] pub holder: Signer<'info>, #[account(has_one = holder)] pub order: Box<Account<'info, Order>>, #[account(mut, close = holder, seeds = [SIZER_SEED, order.key().as_ref()], bump = sizer.bump)] pub sizer: Box<Account<'info, Sizer>>,}- Line 52
s.bump = ctx.bumps.sizer;: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 53
emit!(SizerSet {: Emits an Anchor event that clients can parse from the logs. - Line 66
pub holder: Signer<'info>,: Requires this account to have signed the transaction. - Line 67
#[account(has_one = holder)]: Checks that a stored pubkey field matches the passed account. - Line 69
#[account(mut, close = holder, seeds = [SIZER_SEED, order.key().as_ref()], bump = sizer.bu: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 69
#[account(mut, close = holder, seeds = [SIZER_SEED, order.key().as_ref()], bump = sizer.bu: Closes the account at the end of the instruction and refunds its lamports.
solana/programs/afterbell-vault/src/instructions/admin.rs L229–252 @ d1b583e · MIT · original #[derive(Accounts)]#[instruction(feed_id: [u8; 32])]pub struct AllowFeed<'info> { #[account(mut)] pub authority: Signer<'info>, #[account(has_one = authority)] pub vault: Box<Account<'info, Vault>>, #[account( init_if_needed, payer = authority, space = 8 + FeedConfig::INIT_SPACE, seeds = [FEED_SEED, vault.key().as_ref(), feed_id.as_ref()], bump )] pub feed_config: Box<Account<'info, FeedConfig>>, pub system_program: Program<'info, System>,} pub fn handle_allow_feed(ctx: Context<AllowFeed>, feed_id: [u8; 32], symbol: String, allowed: bool) -> Result<()> { require!(feed_id != [0u8; 32], VaultError::FeedNotAllowed); require!(!symbol.is_empty() && symbol.len() <= MAX_SYMBOL_LEN, VaultError::TooLong); let f = &mut ctx.accounts.feed_config; f.vault = ctx.accounts.vault.key();- Line 234
pub authority: Signer<'info>,: Requires this account to have signed the transaction. - Line 235
#[account(has_one = authority)]: Checks that a stored pubkey field matches the passed account. - Line 238
init_if_needed,: Creates the account only if it doesn't exist. Watch for re-initialisation bugs. - Line 241
seeds = [FEED_SEED, vault.key().as_ref(), feed_id.as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 242
bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 249
require!(feed_id != [0u8; 32], VaultError::FeedNotAllowed);: An explicit validation that aborts the instruction with an error.
solana/programs/afterbell-vault/src/instructions/pool.rs L109–132 @ d1b583e · MIT · original// ------------------------------------------------------------- request_redeem #[derive(Accounts)]pub struct RequestRedeem<'info> { #[account(mut)] pub owner: Signer<'info>, #[account(mut, has_one = share_mint, has_one = share_escrow)] pub vault: Box<Account<'info, Vault>>, #[account( init_if_needed, payer = owner, space = 8 + Request::INIT_SPACE, seeds = [REQUEST_SEED, vault.key().as_ref(), owner.key().as_ref()], bump )] pub request: Box<Account<'info, Request>>, /// The rate of the round that filled an earlier redemption request, if one is waiting. pub rate: Option<Box<Account<'info, RoundRate>>>, pub share_mint: Box<InterfaceAccount<'info, ShareMint>>, #[account(mut, token::mint = share_mint, token::authority = owner, token::token_program = share_token_program)] pub owner_shares: Box<InterfaceAccount<'info, ShareAccount>>, #[account(mut)] pub share_escrow: Box<InterfaceAccount<'info, ShareAccount>>, pub share_token_program: Program<'info, Token2022>,- Line 114
pub owner: Signer<'info>,: Requires this account to have signed the transaction. - Line 115
#[account(mut, has_one = share_mint, has_one = share_escrow)]: Checks that a stored pubkey field matches the passed account. - Line 118
init_if_needed,: Creates the account only if it doesn't exist. Watch for re-initialisation bugs. - Line 121
seeds = [REQUEST_SEED, vault.key().as_ref(), owner.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 122
bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 132
pub share_token_program: Program<'info, Token2022>,: Targets the Token-2022 program instead of the original Token Program.
solana/programs/afterbell-vault/src/instructions/settle.rs L49–72 @ d1b583e · MIT · original series.state = SeriesState::Settled; emit!(SeriesSettled { series: series.key(), price: series.price, publish_time: msg.publish_time }); Ok(())} // ----------------------------------------------------------------- void_series #[derive(Accounts)]pub struct VoidSeries<'info> { pub vault: Box<Account<'info, Vault>>, #[account(mut, has_one = vault)] pub series: Box<Account<'info, Series>>,} /// Permissionless: a series nobody settled within the void delay voids, and/// every premium in it is refunded in full, the keeper's tip included.pub fn handle_void_series(ctx: Context<VoidSeries>) -> Result<()> { let now = Clock::get()?.unix_timestamp; let series = &mut ctx.accounts.series; require!(series.state == SeriesState::Open, VaultError::SeriesNotOpen); let deadline = series.open_ts.checked_add(ctx.accounts.vault.params.void_delay_secs as i64).ok_or(VaultError::MathOverflow)?; require!(now > deadline, VaultError::VoidTooEarly); series.state = SeriesState::Voided;- Line 50
emit!(SeriesSettled { series: series.key(), price: series.price, publish_time: msg.publish: Emits an Anchor event that clients can parse from the logs. - Line 59
#[account(mut, has_one = vault)]: Checks that a stored pubkey field matches the passed account. - Line 66
let now = Clock::get()?.unix_timestamp;: Reads on-chain time from the Clock sysvar. - Line 68
require!(series.state == SeriesState::Open, VaultError::SeriesNotOpen);: An explicit validation that aborts the instruction with an error. - Line 70
series.open_ts.checked_add(ctx.accounts.vault.params.void_delay_secs as i64).ok_or(VaultEr: Uses checked arithmetic so overflow fails instead of wrapping.
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 test | vitest run |
sdk | npm run build | tsup && tsc -p tsconfig.build.json && node fix-types.mjs |
app | npm run dev | next dev |
app | npm run start | next start |
app | npm run build | next build |
solana | anchor build | compile programs and generate IDLs |
solana | anchor test | local validator + test suite |
solana | anchor run test | cargo test |
What you could build from it
- Fork the SuperStack copy and extend it within the DeFi gaps listed on the idea page.
- Learn the pattern first: Program Derived Addresses (PDAs)
- Learn the pattern first: Cross-Program Invocations (CPIs)
- Learn the pattern first: SPL Tokens: mints, accounts and transfers