Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/natureloved/DeadMan-Vault/llms.txt

Use this file to discover all available pages before exploring further.

Beneficiaries are the heart of a Deadman Vault — the people or addresses who receive your locked USDCx if you stop checking in. You can configure one beneficiary or many, assign each a percentage of the vault’s total deposited balance, and optionally delay their payout with a vesting cliff that begins counting down from the moment the vault triggers. The protocol enforces allocation totals, validates Stacks addresses, and handles proportional share calculation automatically during settlement.

The BeneficiaryInput Type

When creating or updating a vault, beneficiaries are specified using the BeneficiaryInput interface:
export interface BeneficiaryInput {
  address: string;          // Stacks principal (e.g. SP2J6Y...)
  name: string;             // Display name
  email?: string;           // Optional — for claim notifications
  allocationPercent: number; // Integer, must sum to 100 across all beneficiaries
  vestingDays: number;      // 0 for immediate payout; >0 for a delayed cliff
}
email is optional. Beneficiaries without an email address still receive their funds automatically — they simply won’t receive a claim-ready notification email. They can manually visit /claim/[vaultId] to check status and trigger settlement if needed.

Allocation Rules

Allocations must sum to 100

initVault enforces that the sum of all allocationPercent values equals exactly 100. Submitting beneficiaries with totals of 99 or 101 will throw: "Beneficiary allocations must total 100%".

Addresses must be valid Stacks principals

Every address is validated with isValidAddress() from the FlowVault SDK before the vault record is created. Invalid addresses cause an immediate error.

Payout Calculation

When the vault is triggered and settleVaultPayout runs, each beneficiary’s share is calculated proportionally from the vault’s total_deposited value. The calculation is done in micro-USDCx (6 decimal places) to avoid floating-point errors:
const totalDepositedMicro = BigInt(Math.round(Number(vault.total_deposited) * 1_000_000));

// Per beneficiary:
const share = (totalDepositedMicro * BigInt(beneficiary.allocation_percent)) / BigInt(totalPercent);
For example, if total_deposited is 1000 USDCx:
Beneficiaryallocation_percentShare (micro-USDCx)Share (USDCx)
Alice70700,000,000700
Bob30300,000,000300
The denominator uses the sum of all beneficiaries’ allocation_percent values (defaulting to 100) to keep rounding consistent across multiple settlement attempts. This means partial retries — where some beneficiaries were already paid in a previous sweep — do not skew the remaining shares.

Vesting Cliffs

Each beneficiary can have a vesting cliff specified in days via vestingDays. A value of 0 means the beneficiary is eligible for immediate payout upon vault trigger. How vesting is scheduled: When the keeper triggers the vault, it records a triggered_at timestamp and computes each beneficiary’s vesting_release_at:
// From scheduleVesting in Supabase queries
vesting_release_at = triggeredAt + vestingDays × 86,400,000 ms
The vesting_release_at column is stored in the beneficiaries table (added in migration 004_vesting_schedule.sql). During each settlement attempt, the keeper filters out beneficiaries whose cliff has not yet matured:
const unpaid = beneficiaries.filter(
  (b) => !b.claimed && (!b.vesting_release_at || new Date(b.vesting_release_at) <= now)
);
Vesting is always calculated relative to triggered_at — the moment the vault flatlined — not from when the keeper cron happened to run. This ensures a 90-day vesting cliff means exactly 90 days after the deadman stopped, regardless of keeper scheduling jitter.

The Beneficiary Database Type

Once stored, each beneficiary has the following shape (from src/types/index.ts):
export interface Beneficiary {
  id: string;
  vault_id: string;
  address: string;
  name: string;
  email?: string;
  allocation_percent: number;
  vesting_days: number;
  vesting_release_at?: string;  // ISO timestamp, set at trigger time
  claimed: boolean;
  claimed_at?: string;          // ISO timestamp of successful payout
  claimed_tx_id?: string;       // On-chain TX ID of the USDCx transfer
  created_at: string;
}
The claimed, claimed_at, and claimed_tx_id fields are updated by recordClaim after each successful sendTokenTransfer. These fields are your audit trail — they prove exactly when and in which transaction each beneficiary was paid.

Example Configuration

Here is an example two-beneficiary setup with a 70%/30% split, where one beneficiary has a 90-day vesting cliff:
[
  {
    "address": "SP2J6Y09JMFWWZCT4VJX0BA5W7A9HZP5EX96Y6VZY",
    "name": "Alice",
    "email": "alice@example.com",
    "allocationPercent": 70,
    "vestingDays": 0
  },
  {
    "address": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM",
    "name": "Bob",
    "email": "bob@example.com",
    "allocationPercent": 30,
    "vestingDays": 90
  }
]
In this configuration:
  • Alice receives 70% of the vault’s balance immediately when the vault triggers.
  • Bob receives 30%, but only after 90 days have elapsed since triggered_at. The keeper will skip Bob on every sweep until his vesting_release_at passes.

Claim Page

Beneficiaries can manually check their vault status and initiate or retry settlement at any time by visiting:
/claim/[vaultId]
This page calls claimInheritance, which verifies the beneficiary’s address against the vault record, calls settleVaultPayout if the beneficiary is still unpaid, and returns the transaction ID once the payout confirms. This serves as a fallback for cases where automatic settlement fails or a beneficiary’s vesting cliff matures between keeper sweeps.
The vault must be in triggered or claimed status for the claim page to process a payout. Attempting to claim on an active or warning vault returns an error: "Vault has not been triggered".

Build docs developers (and LLMs) love