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 directory | Source files |
|---|---|
app | 23 |
programs | 14 |
keeper | 4 |
migrations | 1 |
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.
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 {- Line 53
require!(vault.is_active, VigError::VaultInactive);: An explicit validation that aborts the instruction with an error. - Line 55
let now = Clock::get()?.unix_timestamp;: Reads on-chain time from the Clock sysvar. - Line 62
let bump = vault.bump;: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 69
token::transfer_checked(: A checked token transfer, which also verifies the mint and decimals. - Line 69
token::transfer_checked(: An SPL token transfer through the Token Program. - Line 70
CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
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,- Line 86
.checked_mul(share_bps as u64): Uses checked arithmetic so overflow fails instead of wrapping. - Line 91
require!(amount > 0, VigError::ZeroBalance);: An explicit validation that aborts the instruction with an error. - Line 97
token::transfer_checked(: A checked token transfer, which also verifies the mint and decimals. - Line 97
token::transfer_checked(: An SPL token transfer through the Token Program. - Line 98
CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
programs/afterlife/src/instructions/force_expire.rs L1–24 @ 3c90d46 · MIT · originaluse 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;- 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. - Line 10
bump = vault_config.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 11
has_one = owner @ VigError::Unauthorized,: Checks that a stored pubkey field matches the passed account. - Line 16
pub owner: Signer<'info>,: Requires this account to have signed the transaction. - Line 22
require!(vault.is_active, VigError::VaultInactive);: An explicit validation that aborts the instruction with an error.
programs/afterlife/src/instructions/cancel.rs L1–24 @ 3c90d46 · MIT · originaluse 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(())- 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. - Line 10
bump = vault_config.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 11
has_one = owner @ VigError::Unauthorized,: Checks that a stored pubkey field matches the passed account. - Line 12
close = owner: Closes the account at the end of the instruction and refunds its lamports. - 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.
| Directory | Command | Runs |
|---|---|---|
. | npm run lint | prettier */*.js "*/**/*{.js,.ts}" --check |
keeper | npm run dev | bun --watch src/index.ts |
keeper | npm run start | bun src/index.ts |
app | npm run dev | next dev |
app | npm run start | next start |
app | npm run build | next build |
app | npm run lint | eslint |
. | anchor build | compile programs and generate IDLs |
. | anchor test | local validator + test suite |
. | anchor run test | cargo test |
. | cargo build | Rust workspace build |
. | cargo test | Rust tests |
What you could build from it
- Fork the SuperStack copy and extend it within the Infrastructure & Tooling gaps listed on the idea page.
- Learn the pattern first: Building and sending transactions from a client
- Learn the pattern first: Anchor account constraints