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.

Most issues in Deadman Vault fall into one of three categories: wallet extension conflicts (particularly common when multiple Stacks wallets are installed), environment variable misconfiguration (especially the 32-byte KEEPER_ENCRYPTION_KEY requirement), or infrastructure gaps (keeper cron not running, Hiro API temporarily unavailable). This page covers the most common failure modes with exact fixes, so you can get back to a working vault without digging through server logs.
Cause: Multiple Stacks wallet extensions — most commonly Leather and Xverse — are simultaneously installed and enabled in your browser. Each extension injects its own StacksProvider into the page, and the second injection attempt throws a TypeError: Cannot redefine property: StacksProvider because the property is already non-configurable.Fix:
  1. Open chrome://extensions (or your browser’s equivalent).
  2. Disable all Stacks wallet extensions except the one you want to use.
  3. Hard-refresh the page with Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (macOS) to clear any cached injected scripts.
Important: The useWallet hook in Deadman Vault already suppresses this error from propagating in the browser console (event.preventDefault() is called for errors matching this message). The error does not affect app functionality once the extension is correctly initialized. But if more than one extension is active, window.StacksProvider will be in a conflicted state and wallet connection may fail silently — hence the hard-refresh requirement.
If you see this error alongside the wallet connection working, it’s a cosmetic console noise issue only. If wallet connection is also broken, disable the extra extension and refresh.
Cause: The vault was created and a keeper address was generated, but either:
  • The owner’s USDCx transfer to the keeper address never confirmed on-chain, or
  • The keeper account was never funded with STX for gas fees
Fix:
  1. Check that the USDCx transfer from the owner’s wallet to the keeper address confirmed. Look up the tx_id stored on the vault record in the Stacks testnet explorer: https://explorer.stacks.co/txid/{tx_id}?chain=testnet
  2. If the transfer confirmed but the vault is still stuck in funding, the keeper cron may not have processed it yet. Trigger the fund step manually:
    curl -X POST https://your-app.vercel.app/api/vaults/{vaultId}/fund \
      -H "Content-Type: application/json" \
      -d '{"txId": "the-confirmed-tx-id"}'
    
  3. If the keeper account has no STX for gas, check whether KEEPER_FUNDING_PRIVATE_KEY is set in your environment. If it’s missing, the keeper won’t receive the automated 2,000,000 µSTX (2 STX) gas drip and you’ll need to fund it manually from the Stacks testnet faucet.
The vault cannot be deposited into FlowVault until the keeper account has a non-zero STX balance for gas. Check the keeper address balance in the Stacks explorer before assuming the USDCx transfer is the problem.
Cause: The keeper Vercel cron is either misconfigured, disabled, or the CRON_SECRET environment variable doesn’t match what the cron is sending.Fix:
  1. Verify CRON_SECRET is set in your Vercel project’s environment variables. It must match what’s configured in vercel.json for the cron route.
  2. Check that the cron job is enabled in your Vercel dashboard under Settings → Cron Jobs. Hobby plan projects on Vercel have cron limitations; ensure your plan supports the configured frequency.
  3. Check Vercel function logs for the /api/keeper route to see if the cron is running but failing internally.
  4. To test the keeper cron manually without waiting for the next scheduled run:
    curl -H "Authorization: Bearer $CRON_SECRET" \
      https://your-app.vercel.app/api/keeper
    
    A successful response means the cron logic ran. Check the response body for any vault-level errors.
The keeper cron processes all vaults in active, warning, grace_period, and triggered states on every sweep. If settlements are missing, check that the vault’s status is one of these — vaults in claimed, funding, or created are intentionally excluded from the sweep.
Cause: KEEPER_ENCRYPTION_KEY is missing, empty, or not exactly 64 hexadecimal characters. The getKey() function in src/lib/keeper/crypto.ts enforces this strictly:
function getKey(): Buffer {
  const hex = process.env.KEEPER_ENCRYPTION_KEY;
  if (!hex || hex.length !== 64) {
    throw new Error(
      "KEEPER_ENCRYPTION_KEY must be set to a 32-byte hex string (e.g. `openssl rand -hex 32`)."
    );
  }
  return Buffer.from(hex, "hex");
}
Fix:
  1. Generate a valid key:
    openssl rand -hex 32
    
    This produces exactly 64 lowercase hex characters representing 32 bytes — the correct size for AES-256.
  2. Set it in your environment:
    KEEPER_ENCRYPTION_KEY=<64-hex-character-output>
    
  3. Do not rotate this key in production without re-encrypting all stored keeper key ciphertexts first. If you change KEEPER_ENCRYPTION_KEY while existing vault records have ciphertexts encrypted under the old key, those vaults will throw Malformed keeper key ciphertext or GCM authentication errors at settlement time, and their funds will be inaccessible until re-encrypted. There is no recovery path without the original key.
Key rotation without a migration plan causes permanent loss of access to all affected vault keeper keys. If you must rotate, decrypt and re-encrypt every keeper_key_ciphertext row atomically before deploying the new key.
Cause: A database query is running client-side using the public anon key (NEXT_PUBLIC_SUPABASE_ANON_KEY). All four Deadman Vault tables — vaults, beneficiaries, heartbeats, and notifications — have RLS enabled with zero public policies. The anon key has no access to any of these tables by design.From supabase/migrations/003_enable_rls.sql:
-- No policies are added on purpose — nothing should read/write these
-- tables directly from the browser using the public anon key.
ALTER TABLE vaults ENABLE ROW LEVEL SECURITY;
ALTER TABLE beneficiaries ENABLE ROW LEVEL SECURITY;
ALTER TABLE heartbeats ENABLE ROW LEVEL SECURITY;
ALTER TABLE notifications ENABLE ROW LEVEL SECURITY;
Fix:
  • All database operations must use supabaseAdmin (initialized with SUPABASE_SERVICE_ROLE_KEY) from src/lib/supabase/admin.ts.
  • If you see an RLS error originating from a browser network request, find where that Supabase query is being called from a client component or a use client file and move it to a server-side API route.
  • The service-role key must never be exposed to the client. It is only safe in server-side Next.js code (API routes, Server Actions, server components).
Never prefix SUPABASE_SERVICE_ROLE_KEY with NEXT_PUBLIC_. Doing so would embed it in the browser bundle and expose full database access to any visitor.
Cause: The signDepositTransfer() function in src/lib/flowvault-client.ts sets a 45-second timeout for wallet responses. If the wallet extension doesn’t open a popup or respond within that window, the promise rejects with a timeout error:
const WALLET_RESPONSE_TIMEOUT_MS = 45_000;

const timer = setTimeout(() => {
  reject(
    new Error(
      "Your wallet didn't respond. This usually means more than one Stacks wallet extension is installed and conflicting..."
    )
  );
}, WALLET_RESPONSE_TIMEOUT_MS);
This most commonly happens when multiple Stacks wallet extensions are installed and conflicting over window.StacksProvider, preventing any popup from appearing.Fix:
  1. Ensure only one Stacks wallet extension is enabled (see the StacksProvider conflict issue above).
  2. Refresh the page and retry — if the extension is in a bad state from a prior conflict, a fresh page load reinitializes it.
  3. If the wallet popup appears but the user doesn’t interact within 45 seconds, the request also times out. Simply retry the action.
The 45-second timeout is intentional. Without it, a missing wallet would hang on “Confirm in wallet…” indefinitely with no feedback. The timeout provides a clear error message with actionable instructions.
Cause: The keeper layer throws an error — rather than returning 0 — when the Hiro API is unavailable and the current Stacks block height cannot be fetched. This is intentional defensive behavior.If the keeper returned 0 as a fallback block height, every vault would appear to have approximately 4 million blocks remaining on its deadline (since 0 < heartbeatDeadlineBlock for every real vault). The deadman switch would never fire regardless of how overdue the vault actually is, masking missed deadlines for the entire duration of the outage.Behavior: The keeper cron run fails with an error logged, but no vault state is mutated. Vault statuses are not changed, no settlements are attempted, and no emails are sent.Fix:
  1. Check the status of the Hiro Stacks API: https://status.hiro.so
  2. Wait for the API to recover.
  3. The next scheduled keeper cron run will automatically proceed normally once the block height is fetchable again.
  4. No manual intervention is needed — vaults are not harmed by a cron run that exits early.
Missed cron runs do not cause vaults to trigger incorrectly. A vault only transitions to grace_period or triggered when the keeper successfully confirms that currentBlock > heartbeatDeadlineBlock. If block height is unavailable, that check never runs.
Cause: One or both of the required Resend environment variables are missing or misconfigured.Fix:
  1. Verify both variables are set in your environment:
    RESEND_API_KEY=re_xxxxxxxxxxxx
    RESEND_FROM_EMAIL=noreply@yourdomain.com
    
  2. Confirm that RESEND_FROM_EMAIL uses a domain that has been verified in your Resend dashboard. Resend rejects sends from unverified sender domains.
  3. Check server logs for "RESEND not configured, skipping email" — this warning appears when either variable is absent. It confirms the notification was skipped, not that it failed.
  4. If variables are correctly set but emails still aren’t arriving, check your Resend dashboard’s Emails log for delivery status and any bounce or rejection events.
Important: Email delivery is never required for vault correctness. Vaults trigger, settle, and distribute funds regardless of email configuration. If notifications are not sending, vault owners and beneficiaries simply won’t receive email alerts — but their funds are unaffected.

Build docs developers (and LLMs) love