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 /api/keeper endpoint is the heartbeat of the Deadman Vault protocol. It is designed to be called on a schedule — typically via a Vercel Cron Job — and performs the full vault lifecycle sweep on every invocation. It fetches the current Stacks testnet block height, then evaluates every vault in an actionable state (active, warning, grace_period, or triggered) against that block height, advancing states, firing notification emails, and settling payouts to beneficiaries as deadlines are crossed.
This endpoint can take up to 60 seconds to complete. Settling multiple triggered vaults involves on-chain withdrawal and per-beneficiary token transfers, which can chain into several network round-trips. Per-vault errors are isolated — a failure on one vault does not abort the sweep for others.

Endpoint

GET /api/keeper

Authentication

If the CRON_SECRET environment variable is set, every request to this endpoint must include a matching Authorization header. Requests that omit or mismatch the secret receive a 401 Unauthorized response immediately, before any vault work is performed.
Vercel Cron Jobs automatically inject the Authorization: Bearer <CRON_SECRET> header when CRON_SECRET is configured in your project environment. No extra setup is needed for scheduled invocations.
HeaderValue
AuthorizationBearer <CRON_SECRET>
If CRON_SECRET is not set in the environment, the authentication check is skipped entirely and the endpoint is open.

Cron Schedule

The production deployment configures this endpoint as a Vercel Cron Job in vercel.json:
{
  "crons": [
    {
      "path": "/api/keeper",
      "schedule": "0 0 * * *"
    }
  ]
}
The schedule 0 0 * * * runs the sweep once per day at 00:00 UTC. At the Stacks target rate of 144 blocks per day, a single daily sweep is sufficient to advance vault states and catch deadlines as they expire. If you need a tighter detection window, reduce the cron interval — but bear in mind the maxDuration of 60 seconds limits how long a single invocation may run.

Sweep Behaviour

On each invocation, runKeeper(currentBlock) processes vaults in the following order: 1. Retry settlement for already-triggered vaults For each vault in triggered status, if any beneficiary record has claimed = false, settleVaultPayout() is called again. This handles beneficiaries whose initial payout failed (e.g. due to a transient network error) and beneficiaries whose vesting cliff has matured since the last sweep. Once every beneficiary is paid, the vault is automatically moved to claimed status. A settle-retry:<vaultId> action is logged on success. 2. Advance to warning If deadline - currentBlock <= 432 (within ~3 days) and the deadline has not yet passed, the vault is advanced to warning status. A warning email is sent to the owner, deduplicated so only one warning fires per 24-hour window. A warning:<vaultId> action is logged. 3. Advance to grace_period If currentBlock > deadline and currentBlock <= deadline + 288 (within the ~2-day grace window), the vault is advanced to grace_period status. A grace-period email is sent to the owner, deduplicated to once per 48-hour window. A grace:<vaultId> action is logged. 4. Advance to triggered and settle If currentBlock > deadline + 288, the vault is flatlined:
  • Status is set to triggered and triggered_at is recorded.
  • Vesting schedules are computed and written for all beneficiaries.
  • settleVaultPayout() is called immediately with the freshly-scheduled beneficiary list.
  • A claim-ready email is sent to every beneficiary.
  • A triggered:<vaultId> action is logged.
The 288-block grace period corresponds to approximately 2 days at the Stacks target block rate of 144 blocks per day. Owners who miss the deadline but check in before this window closes will still prevent their vault from triggering.

Response Fields

checked
number
Total number of vaults evaluated during this sweep, regardless of whether any action was taken on them.
currentBlock
number
The Stacks testnet block height fetched from the Hiro API at the start of this invocation. All deadline comparisons during the sweep used this value.
actions
string[]
An ordered list of action log entries recorded during the sweep. Each entry is a colon-separated string of the form <action>:<vaultId>. Possible prefixes are settle-retry, warning, grace, and triggered.
timestamp
string
ISO 8601 timestamp of when this sweep response was generated, e.g. "2025-01-15T10:30:00.000Z".

Example Request

Authenticated (production)
curl --request GET \
  --url https://your-app.vercel.app/api/keeper \
  --header 'Authorization: Bearer your-cron-secret-here'
Unauthenticated (local development, no CRON_SECRET set)
curl --request GET \
  --url http://localhost:3000/api/keeper

Example Response

{
  "checked": 3,
  "currentBlock": 182340,
  "actions": [
    "warning:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "triggered:def45678-9012-3456-bcde-f12345678901"
  ],
  "timestamp": "2025-01-15T10:30:00.000Z"
}
When no vaults require action:
{
  "checked": 5,
  "currentBlock": 182401,
  "actions": [],
  "timestamp": "2025-01-15T11:00:00.000Z"
}

Error Responses

StatusCondition
401CRON_SECRET is set and the Authorization header is missing or does not match
500The Hiro API is unavailable and the current block height cannot be fetched
500An unexpected error causes the entire sweep to fail
A 500 response means the sweep itself failed before or during processing — no vault states will have been updated. Individual per-vault errors during the sweep are caught, logged to the server console, and skipped without affecting the overall response.
The block height lookup deliberately throws rather than returning a fallback value of 0 on API failure. If 0 were returned, every vault’s deadline (currentBlock > deadline) would never fire — all vaults would appear to have billions of blocks remaining regardless of how overdue they are, and the deadman switch would silently stop working. The 500 surfaces the Hiro API outage explicitly so you can alert on it rather than mask it.
Error responses follow the shape:
{
  "error": "Human-readable error message"
}

Build docs developers (and LLMs) love