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.

The heartbeat is the core proof-of-life primitive of Deadman Vault. Rather than relying on a password, a signed message, or a centralized ping — all of which can fail silently — the heartbeat requires the vault owner to make a small, verifiable on-chain action: sending exactly 0.01 USDCx to the vault’s custodial keeper address before the current deadline expires. This on-chain signal is unforgeable, timestamped to the block, and triggers an automatic extension of the FlowVault lock on the owner’s behalf. Miss the deadline long enough, and the vault settles to beneficiaries. Keep checking in, and the funds remain under the owner’s control indefinitely.

What Happens During a Heartbeat

A heartbeat is a two-step operation performed by the keeper on behalf of the owner:
1

Owner sends 0.01 USDCx to the keeper address

The owner initiates a USDCx transfer of exactly HEARTBEAT_AMOUNT (0.01 USDCx) to the vault’s dedicated keeper address. This transfer’s transaction ID is passed to the heartbeat endpoint as fundingTxId.
2

Keeper extends the FlowVault lock (lock-stacking)

The keeper decrypts its private key, computes the new deadline block, and calls FlowVault’s setRoutingRules() with the updated lockUntilBlock. It then calls deposit() to commit the lock extension. This is the lock-stacking primitive — the existing locked balance’s deadline is pushed forward without withdrawing and re-depositing.
3

Deadline is updated in the database

updateVaultDeadline records the new last_heartbeat_block and heartbeat_deadline_block in the vault record. The heartbeat event itself is logged to the heartbeats table with the block height, transaction ID, and deposited amount.

Deadline Calculation

The new deadline is always computed relative to the current block height at the time the heartbeat is processed — not from the old deadline. This means a late-but-valid check-in still resets the full interval:
const currentBlock = await flowVault.getCurrentBlockHeight(vault.keeper_address);
const newDeadlineBlock = currentBlock + vault.heartbeat_interval_days * BLOCKS_PER_DAY;
Where BLOCKS_PER_DAY = 144 (approximately 10 minutes per block on Stacks). For example, with a 30-day interval:
newDeadlineBlock = currentBlock + (30 × 144) = currentBlock + 4,320 blocks

Lock-Stacking in Detail

Under the hood, the heartbeat reuses FlowVault’s routing rules to extend the lock without moving funds. The sequence is:
await flowVault.setRoutingRules({
  lockAmount: tokenToMicro(HEARTBEAT_AMOUNT), // 10,000 micro-USDCx
  lockUntilBlock: newDeadlineBlock,
  splitAddress: null,
  splitAmount: tokenToMicro("0"),
});
return flowVault.deposit(tokenToMicro(HEARTBEAT_AMOUNT));
The lockUntilBlock is set to the freshly computed deadline before each deposit() call. FlowVault’s lock-stacking primitive accumulates locked balance and always respects the latest lockUntilBlock rule — so this pattern safely extends the effective deadline on every heartbeat without disturbing the principal.
The 0.01 USDCx sent per heartbeat is the minimum required to trigger a deposit() call in FlowVault. This amount accumulates in the FlowVault balance over time alongside the original deposit. All accumulated balance is withdrawn and distributed during settlement.

Heartbeat Interval Options

When creating a vault, the owner selects a heartbeatIntervalDays value. This determines how frequently the owner must check in. The available preset options are:
Labelvalue (days)Blocks per intervalApproximate check-in frequency
7 days71,008Weekly
14 days142,016Bi-weekly
1 month304,320Monthly
2 months608,640Every 2 months
3 months9012,960Quarterly
6 months18025,920Bi-annually
1 year36552,560Annually
2 years730105,120Every 2 years
These values come directly from HEARTBEAT_OPTIONS in src/lib/constants.ts.

What Happens If You Miss a Heartbeat

Missing a heartbeat does not immediately trigger your vault. The protocol provides two escalating warning windows:
1

Warning window (432 blocks before deadline)

When deadline − currentBlock ≤ 432 (≈ 3 days), the keeper sends a warning email to the vault owner and advances the vault status to warning. The owner can still perform a heartbeat to recover.
2

Grace period (0–288 blocks after deadline)

Once currentBlock > deadline, the vault enters grace_period. The keeper sends a critical grace period email. The owner has approximately 2 more days (GRACE_PERIOD_BLOCKS = 288) to send a heartbeat and recover.
3

Trigger (288+ blocks after deadline)

When currentBlock > deadline + 288, recovery is no longer possible. The keeper triggers the vault, schedules vesting for all beneficiaries, and begins settlement.
Check in well before your deadline. The warning fires 3 days before expiry, but network congestion, keeper delays, or email delivery issues mean you should aim to send your heartbeat at least a week early. Consider setting calendar reminders at both the 2-week and 1-week mark before each deadline, regardless of your chosen interval.

The performHeartbeat Function

The heartbeat logic lives in src/lib/deadman-vault/heartbeat.ts. It accepts a HeartbeatParams object and returns the on-chain transaction ID and updated deadline:
import { performHeartbeat } from "@/lib/deadman-vault/heartbeat";

const result = await performHeartbeat({
  vaultId: "uuid-of-your-vault",
  fundingTxId: "0xabc123...", // TX ID of the 0.01 USDCx transfer you sent
});

console.log(result.txId);           // FlowVault lock-extension transaction
console.log(result.newDeadlineBlock); // e.g. 184320
performHeartbeat is idempotent in intent but not strictly so — each successful call does extend the lock. Do not blindly retry after a confirmed success. The function will throw if fundingTxId is missing or if the vault has no keeper account configured.

Build docs developers (and LLMs) love