Sol.College

Full lesson

afterlife

Dead man's switch on Solana: automatic on-chain crypto inheritance for your heirs if you stop checking in.

What it does

From the project README:

1. **Setup.** Connect a Solana wallet, choose a check-in interval (30, 60 or 90 days) and an optional grace period, add up to 5 heirs by email with percentage splits, and pick a legacy strategy. SOL is wrapped to wSOL and the vault program is approved as delegate over it, so funds stay in your wallet until the switch fires. 2. **Check in.** Open the dashboard and check in to reset the countdown. Each check-in carries a small protocol fee (0.005 SOL). 3. **Distribution.** Once the timer and grace period run out, `execute_distribution` can be called by anyone. A keeper does it automatically, mov
Original
Feli2arias/afterlife
SuperStack fork
Blockchains/afterlife @ 3c90d46
Stars
3
Last push (original)
2026-10-05
Languages
TypeScript, Rust, CSS, JavaScript
Detected stack
@solana/kitAnchorNext.jsweb3.js
Categories
Infrastructure & Tooling
Licence
MIT
Homepage
https://afterlife-sol.vercel.app

Architecture

42 source files. By extension: .tsx ×16, .rs ×14, .ts ×12.

Top-level directorySource files
app23
programs14
keeper4
migrations1

On-chain programs: programs/afterlife

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/afterlife/. 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.

Feli2arias/afterlife · programs/afterlife/src/instructions/execute.rs L49–72 @ 3c90d46 · MIT · original
 pub fn handler(ctx: Context<ExecuteDistribution>) -> Result<()> {    let vault = &ctx.accounts.vault_config;     require!(vault.is_active, VigError::VaultInactive);     let now = Clock::get()?.unix_timestamp;    require!(vault.is_expired(now), VigError::TimerNotExpired);     let total_amount = ctx.accounts.owner_token_account.amount;    require!(total_amount > 0, VigError::ZeroBalance);     let owner_key = vault.owner;    let bump = vault.bump;    let token_mint_key = ctx.accounts.token_mint.key();    let decimals = ctx.accounts.token_mint.decimals;     // vault_config PDA is the approved SPL delegate — sign with its seeds    let seeds: &[&[u8]] = &[b"vigil", owner_key.as_ref(), &[bump]];     token::transfer_checked(        CpiContext::new_with_signer(            ctx.accounts.token_program.key(),            TransferChecked {
  1. Line 53 require!(vault.is_active, VigError::VaultInactive);: An explicit validation that aborts the instruction with an error.
  2. Line 55 let now = Clock::get()?.unix_timestamp;: Reads on-chain time from the Clock sysvar.
  3. Line 62 let bump = vault.bump;: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  4. Line 69 token::transfer_checked(: A checked token transfer, which also verifies the mint and decimals.
  5. Line 69 token::transfer_checked(: An SPL token transfer through the Token Program.
  6. Line 70 CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
Feli2arias/afterlife · programs/afterlife/src/instructions/claim.rs L85–108 @ 3c90d46 · MIT · original
    let amount = executed_total        .checked_mul(share_bps as u64)        .ok_or(VigError::ArithmeticOverflow)?        .checked_div(10_000)        .ok_or(VigError::ArithmeticOverflow)?;     require!(amount > 0, VigError::ZeroBalance);     let vault_config_key = ctx.accounts.vault_config.key();    let vault_auth_bump = ctx.bumps.vault_authority;    let vault_auth_seeds: &[&[u8]] = &[b"vault_auth", vault_config_key.as_ref(), &[vault_auth_bump]];     token::transfer_checked(        CpiContext::new_with_signer(            ctx.accounts.token_program.key(),            TransferChecked {                from: ctx.accounts.vault_token_account.to_account_info(),                mint: ctx.accounts.token_mint.to_account_info(),                to: ctx.accounts.heir_token_account.to_account_info(),                authority: ctx.accounts.vault_authority.to_account_info(),            },            &[vault_auth_seeds],        ),        amount,
  1. Line 86 .checked_mul(share_bps as u64): Uses checked arithmetic so overflow fails instead of wrapping.
  2. Line 91 require!(amount > 0, VigError::ZeroBalance);: An explicit validation that aborts the instruction with an error.
  3. Line 97 token::transfer_checked(: A checked token transfer, which also verifies the mint and decimals.
  4. Line 97 token::transfer_checked(: An SPL token transfer through the Token Program.
  5. Line 98 CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
Feli2arias/afterlife · programs/afterlife/src/instructions/force_expire.rs L1–24 @ 3c90d46 · MIT · original
use anchor_lang::prelude::*;use crate::state::VaultConfig;use crate::errors::VigError; #[derive(Accounts)]pub struct ForceExpire<'info> {    #[account(        mut,        seeds = [b"vigil", owner.key().as_ref()],        bump = vault_config.bump,        has_one = owner @ VigError::Unauthorized,    )]    pub vault_config: Account<'info, VaultConfig>,     #[account(mut)]    pub owner: Signer<'info>,} // Backdates last_checkin so the vault appears expired — for demos and testing onlypub fn handler(ctx: Context<ForceExpire>) -> Result<()> {    let vault = &mut ctx.accounts.vault_config;    require!(vault.is_active, VigError::VaultInactive);    let interval_secs = vault.interval_days as i64 * 86_400;    let grace_secs = vault.grace_period_days as i64 * 86_400;
  1. Line 9 seeds = [b"vigil", owner.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  2. Line 10 bump = vault_config.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  3. Line 11 has_one = owner @ VigError::Unauthorized,: Checks that a stored pubkey field matches the passed account.
  4. Line 16 pub owner: Signer<'info>,: Requires this account to have signed the transaction.
  5. Line 22 require!(vault.is_active, VigError::VaultInactive);: An explicit validation that aborts the instruction with an error.
Feli2arias/afterlife · programs/afterlife/src/instructions/cancel.rs L1–24 @ 3c90d46 · MIT · original
use anchor_lang::prelude::*;use crate::state::VaultConfig;use crate::errors::VigError; #[derive(Accounts)]pub struct Cancel<'info> {    #[account(        mut,        seeds = [b"vigil", owner.key().as_ref()],        bump = vault_config.bump,        has_one = owner @ VigError::Unauthorized,        close = owner    )]    pub vault_config: Account<'info, VaultConfig>,     #[account(mut)]    pub owner: Signer<'info>,     pub system_program: Program<'info, System>,} pub fn handler(_ctx: Context<Cancel>) -> Result<()> {    // `close = owner` returns rent to owner regardless of active state    Ok(())
  1. Line 9 seeds = [b"vigil", owner.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  2. Line 10 bump = vault_config.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  3. Line 11 has_one = owner @ VigError::Unauthorized,: Checks that a stored pubkey field matches the passed account.
  4. Line 12 close = owner: Closes the account at the end of the instruction and refunds its lamports.
  5. Line 17 pub owner: Signer<'info>,: Requires this account to have signed the transaction.

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.

DirectoryCommandRuns
.npm run lintprettier */*.js "*/**/*{.js,.ts}" --check
keepernpm run devbun --watch src/index.ts
keepernpm run startbun src/index.ts
appnpm run devnext dev
appnpm run startnext start
appnpm run buildnext build
appnpm run linteslint
.anchor buildcompile programs and generate IDLs
.anchor testlocal validator + test suite
.anchor run testcargo test
.cargo buildRust workspace build
.cargo testRust tests

What you could build from it

Used in lessons