{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "lock-bsv",
  "title": "Lock BSV",
  "author": "Satchmo <https://bigblocks.dev>",
  "description": "Time-lock BSV until a future block height with lock form, lock summary, and unlock for matured locks via @1sat/actions lockBsv and unlockBsv",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "badge",
    "button",
    "card",
    "input",
    "label",
    "separator",
    "skeleton"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/lock-bsv/index.tsx",
      "content": "\"use client\"\n\nimport { useLockBsv } from \"./use-lock-bsv\"\nimport { LockBsvUi } from \"./lock-bsv-ui\"\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport { useLockBsv } from \"./use-lock-bsv\"\nexport { LockBsvUi } from \"./lock-bsv-ui\"\nexport type {\n  LockData,\n  LockParams,\n  LockOperationResult,\n  UseLockBsvOptions,\n  UseLockBsvReturn,\n} from \"./use-lock-bsv\"\nexport type { LockBsvUiProps } from \"./lock-bsv-ui\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Props for the composed LockBsv block */\nexport interface LockBsvProps {\n  /** Pre-populated lock summary data */\n  lockData?: {\n    totalLocked: number\n    unlockable: number\n    nextUnlock: number\n  }\n  /** Execute a lock operation (connect to @1sat/actions lockBsv) */\n  onLock?: (params: {\n    satoshis: number\n    until: number\n  }) => Promise<{ txid?: string; error?: string }>\n  /** Execute an unlock of matured locks (connect to @1sat/actions unlockBsv) */\n  onUnlock?: () => Promise<{ txid?: string; error?: string }>\n  /** Callback on successful lock or unlock */\n  onSuccess?: (result: { txid?: string; error?: string }) => void\n  /** Callback on error */\n  onError?: (error: Error) => void\n  /** Additional CSS classes */\n  className?: string\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n/**\n * Time-lock BSV block: summary card, lock form, and unlock action.\n *\n * Wire `onLock` and `onUnlock` to `@1sat/actions` lock operations:\n *\n * @example\n * ```tsx\n * import { LockBsv } from \"@/components/blocks/lock-bsv\"\n * import { lockBsv, unlockBsv, createContext } from \"@1sat/actions\"\n * import { useWallet } from \"@1sat/react\"\n *\n * function App() {\n *   const { wallet } = useWallet()\n *   const ctx = createContext(wallet)\n *\n *   return (\n *     <LockBsv\n *       onLock={(params) =>\n *         lockBsv.execute(ctx, { requests: [params] })\n *       }\n *       onUnlock={() => unlockBsv.execute(ctx)}\n *     />\n *   )\n * }\n * ```\n */\nexport function LockBsv({\n  lockData: initialLockData,\n  onLock,\n  onUnlock,\n  onSuccess,\n  onError,\n  className,\n}: LockBsvProps) {\n  const { lockData, isLocking, isUnlocking, error, lastResult, lock, unlock, reset } =\n    useLockBsv({\n      lockData: initialLockData,\n      onLock,\n      onUnlock,\n      onSuccess,\n      onError,\n    })\n\n  return (\n    <LockBsvUi\n      lockData={lockData}\n      isLocking={isLocking}\n      isUnlocking={isUnlocking}\n      error={error}\n      lastResult={lastResult}\n      onLock={lock}\n      onUnlock={unlock}\n      onReset={reset}\n      className={className}\n    />\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/lock-bsv/index.tsx"
    },
    {
      "path": "registry/new-york/blocks/lock-bsv/lock-bsv-ui.tsx",
      "content": "\"use client\"\n\nimport { useCallback, useState } from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport {\n  Lock,\n  Unlock,\n  Clock,\n  AlertCircle,\n  Check,\n  Loader2,\n  Blocks,\n} from \"lucide-react\"\nimport type { LockData, LockParams, LockOperationResult } from \"./use-lock-bsv\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Props for the LockBsvUi presentation component */\nexport interface LockBsvUiProps {\n  /** Current lock summary data */\n  lockData: LockData\n  /** Whether a lock operation is in progress */\n  isLocking: boolean\n  /** Whether an unlock operation is in progress */\n  isUnlocking: boolean\n  /** Last operation error */\n  error: Error | null\n  /** Last successful result */\n  lastResult: LockOperationResult | null\n  /** Callback to execute a lock */\n  onLock: (params: LockParams) => Promise<void>\n  /** Callback to execute an unlock */\n  onUnlock: () => Promise<void>\n  /** Callback to clear error / result */\n  onReset?: () => void\n  /** Additional CSS classes */\n  className?: string\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatSatoshis(sats: number): string {\n  if (sats >= 100000000) {\n    return `${(sats / 100000000).toFixed(8)} BSV`\n  }\n  return `${sats.toLocaleString()} sats`\n}\n\nfunction formatBlockHeight(height: number): string {\n  return height.toLocaleString()\n}\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nfunction LockSummary({\n  lockData,\n  isLoading,\n}: {\n  lockData: LockData\n  isLoading: boolean\n}) {\n  if (isLoading) {\n    return (\n      <div className=\"grid grid-cols-3 gap-4\">\n        <div className=\"flex flex-col gap-1.5\">\n          <Skeleton className=\"h-3 w-16\" />\n          <Skeleton className=\"h-5 w-24\" />\n        </div>\n        <div className=\"flex flex-col gap-1.5\">\n          <Skeleton className=\"h-3 w-16\" />\n          <Skeleton className=\"h-5 w-24\" />\n        </div>\n        <div className=\"flex flex-col gap-1.5\">\n          <Skeleton className=\"h-3 w-16\" />\n          <Skeleton className=\"h-5 w-24\" />\n        </div>\n      </div>\n    )\n  }\n\n  return (\n    <div className=\"grid grid-cols-3 gap-4\">\n      <div className=\"flex flex-col gap-1\">\n        <span className=\"text-xs text-muted-foreground\">Total Locked</span>\n        <span className=\"text-sm font-semibold tabular-nums\">\n          {formatSatoshis(lockData.totalLocked)}\n        </span>\n      </div>\n      <div className=\"flex flex-col gap-1\">\n        <span className=\"text-xs text-muted-foreground\">Unlockable</span>\n        <span className=\"text-sm font-semibold tabular-nums\">\n          {lockData.unlockable > 0 ? (\n            <span className=\"text-primary\">\n              {formatSatoshis(lockData.unlockable)}\n            </span>\n          ) : (\n            formatSatoshis(0)\n          )}\n        </span>\n      </div>\n      <div className=\"flex flex-col gap-1\">\n        <span className=\"text-xs text-muted-foreground\">Next Unlock</span>\n        <span className=\"text-sm font-semibold tabular-nums\">\n          {lockData.nextUnlock > 0 ? (\n            <span className=\"flex items-center gap-1\">\n              <Blocks data-icon className=\"size-3 text-muted-foreground\" />\n              {formatBlockHeight(lockData.nextUnlock)}\n            </span>\n          ) : (\n            <span className=\"text-muted-foreground\">&mdash;</span>\n          )}\n        </span>\n      </div>\n    </div>\n  )\n}\n\nfunction LockForm({\n  isLocking,\n  onLock,\n}: {\n  isLocking: boolean\n  onLock: (params: LockParams) => Promise<void>\n}) {\n  const [satoshis, setSatoshis] = useState(\"\")\n  const [blockHeight, setBlockHeight] = useState(\"\")\n  const [validationError, setValidationError] = useState<string | null>(null)\n\n  const handleSubmit = useCallback(\n    async (e: React.FormEvent<HTMLFormElement>) => {\n      e.preventDefault()\n      setValidationError(null)\n\n      const sats = Number.parseInt(satoshis, 10)\n      if (Number.isNaN(sats) || sats <= 0) {\n        setValidationError(\"Amount must be a positive number of satoshis\")\n        return\n      }\n\n      const until = Number.parseInt(blockHeight, 10)\n      if (Number.isNaN(until) || until <= 0) {\n        setValidationError(\"Block height must be a positive integer\")\n        return\n      }\n\n      await onLock({ satoshis: sats, until })\n      setSatoshis(\"\")\n      setBlockHeight(\"\")\n    },\n    [satoshis, blockHeight, onLock]\n  )\n\n  return (\n    <form onSubmit={handleSubmit} className=\"flex flex-col gap-4\">\n      <div className=\"grid gap-4 sm:grid-cols-2\">\n        <div className=\"flex flex-col gap-2\">\n          <Label htmlFor=\"lock-satoshis\">Amount (sats)</Label>\n          <Input\n            id=\"lock-satoshis\"\n            type=\"number\"\n            min={1}\n            placeholder=\"10000\"\n            value={satoshis}\n            onChange={(e) => setSatoshis(e.target.value)}\n            disabled={isLocking}\n            aria-describedby={\n              validationError ? \"lock-validation-error\" : undefined\n            }\n          />\n        </div>\n        <div className=\"flex flex-col gap-2\">\n          <Label htmlFor=\"lock-block-height\">Lock Until (block height)</Label>\n          <Input\n            id=\"lock-block-height\"\n            type=\"number\"\n            min={1}\n            placeholder=\"890000\"\n            value={blockHeight}\n            onChange={(e) => setBlockHeight(e.target.value)}\n            disabled={isLocking}\n            aria-describedby={\n              validationError ? \"lock-validation-error\" : undefined\n            }\n          />\n        </div>\n      </div>\n\n      {validationError && (\n        <p\n          id=\"lock-validation-error\"\n          className=\"flex items-center gap-1.5 text-sm text-destructive\"\n        >\n          <AlertCircle data-icon className=\"size-3.5\" />\n          {validationError}\n        </p>\n      )}\n\n      <Button\n        type=\"submit\"\n        disabled={isLocking || !satoshis || !blockHeight}\n        className=\"w-full\"\n      >\n        {isLocking ? (\n          <>\n            <Loader2 data-icon className=\"animate-spin\" />\n            Locking...\n          </>\n        ) : (\n          <>\n            <Lock data-icon />\n            Lock BSV\n          </>\n        )}\n      </Button>\n    </form>\n  )\n}\n\nfunction UnlockSection({\n  unlockable,\n  isUnlocking,\n  onUnlock,\n}: {\n  unlockable: number\n  isUnlocking: boolean\n  onUnlock: () => Promise<void>\n}) {\n  if (unlockable <= 0) return null\n\n  return (\n    <>\n      <Separator />\n      <div className=\"flex items-center justify-between gap-4\">\n        <div className=\"flex flex-col gap-0.5\">\n          <span className=\"text-sm font-medium\">Ready to Unlock</span>\n          <span className=\"text-xs text-muted-foreground\">\n            {formatSatoshis(unlockable)} available\n          </span>\n        </div>\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          disabled={isUnlocking}\n          onClick={onUnlock}\n        >\n          {isUnlocking ? (\n            <>\n              <Loader2 data-icon className=\"animate-spin\" />\n              Unlocking...\n            </>\n          ) : (\n            <>\n              <Unlock data-icon />\n              Unlock\n            </>\n          )}\n        </Button>\n      </div>\n    </>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Main UI\n// ---------------------------------------------------------------------------\n\n/**\n * Pure presentation for the Lock BSV block.\n *\n * Receives lock state and callbacks from the `useLockBsv` hook.\n * Never imports SDK packages directly.\n */\nexport function LockBsvUi({\n  lockData,\n  isLocking,\n  isUnlocking,\n  error,\n  lastResult,\n  onLock,\n  onUnlock,\n  onReset,\n  className,\n}: LockBsvUiProps) {\n  return (\n    <Card className={cn(\"w-full max-w-md\", className)}>\n      <CardHeader>\n        <div className=\"flex items-center justify-between\">\n          <CardTitle className=\"flex items-center gap-2\">\n            <Clock data-icon className=\"size-5 text-muted-foreground\" />\n            Lock BSV\n          </CardTitle>\n          {lockData.totalLocked > 0 && (\n            <Badge variant=\"secondary\">\n              {formatSatoshis(lockData.totalLocked)} locked\n            </Badge>\n          )}\n        </div>\n        <CardDescription>\n          Time-lock satoshis until a future block height\n        </CardDescription>\n      </CardHeader>\n\n      <CardContent className=\"flex flex-col gap-4\">\n        <LockSummary lockData={lockData} isLoading={false} />\n        <Separator />\n        <LockForm isLocking={isLocking} onLock={onLock} />\n        <UnlockSection\n          unlockable={lockData.unlockable}\n          isUnlocking={isUnlocking}\n          onUnlock={onUnlock}\n        />\n      </CardContent>\n\n      {(error || lastResult?.txid) && (\n        <CardFooter className=\"flex flex-col gap-2\">\n          {error && (\n            <div className=\"flex w-full items-start gap-2 rounded-md border border-destructive/50 bg-destructive/10 px-3 py-2\">\n              <AlertCircle\n                data-icon\n                className=\"mt-0.5 size-4 shrink-0 text-destructive\"\n              />\n              <div className=\"flex flex-col gap-1\">\n                <span className=\"text-sm font-medium text-destructive\">\n                  {error.message}\n                </span>\n                {onReset && (\n                  <button\n                    type=\"button\"\n                    onClick={onReset}\n                    className=\"text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground transition-colors\"\n                  >\n                    Dismiss\n                  </button>\n                )}\n              </div>\n            </div>\n          )}\n\n          {lastResult?.txid && !error && (\n            <div className=\"flex w-full items-start gap-2 rounded-md border border-primary/30 bg-primary/5 px-3 py-2\">\n              <Check\n                data-icon\n                className=\"mt-0.5 size-4 shrink-0 text-primary\"\n              />\n              <div className=\"flex flex-col gap-1\">\n                <span className=\"text-sm font-medium\">\n                  Transaction confirmed\n                </span>\n                <span className=\"break-all font-mono text-xs text-muted-foreground\">\n                  {lastResult.txid}\n                </span>\n              </div>\n            </div>\n          )}\n        </CardFooter>\n      )}\n    </Card>\n  )\n}\n",
      "type": "registry:component",
      "target": "~/components/blocks/lock-bsv/lock-bsv-ui.tsx"
    },
    {
      "path": "registry/new-york/blocks/lock-bsv/use-lock-bsv.ts",
      "content": "import { useCallback, useState } from \"react\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Summary of a user's time-locked BSV */\nexport interface LockData {\n  /** Total satoshis currently locked */\n  totalLocked: number\n  /** Satoshis available to unlock (matured locks) */\n  unlockable: number\n  /** Block height of the next lock expiry (0 = no pending locks) */\n  nextUnlock: number\n}\n\n/** Parameters for a lock operation */\nexport interface LockParams {\n  /** Amount in satoshis to lock */\n  satoshis: number\n  /** Block height until which to lock */\n  until: number\n}\n\n/** Result from a lock or unlock operation */\nexport interface LockOperationResult {\n  /** Transaction ID on success */\n  txid?: string\n  /** Error message on failure */\n  error?: string\n}\n\n/** Options passed to the useLockBsv hook */\nexport interface UseLockBsvOptions {\n  /** Pre-populated lock data (skips initial fetch) */\n  lockData?: LockData\n  /** Callback invoked when a lock or unlock succeeds */\n  onSuccess?: (result: LockOperationResult) => void\n  /** Callback invoked when a lock or unlock fails */\n  onError?: (error: Error) => void\n  /** Execute a lock operation (connect to @1sat/actions lockBsv) */\n  onLock?: (params: LockParams) => Promise<LockOperationResult>\n  /** Execute an unlock of matured locks (connect to @1sat/actions unlockBsv) */\n  onUnlock?: () => Promise<LockOperationResult>\n}\n\n/** Return value of the useLockBsv hook */\nexport interface UseLockBsvReturn {\n  /** Current lock summary */\n  lockData: LockData\n  /** Whether a lock operation is in progress */\n  isLocking: boolean\n  /** Whether an unlock operation is in progress */\n  isUnlocking: boolean\n  /** Last operation error */\n  error: Error | null\n  /** Last successful result (lock or unlock) */\n  lastResult: LockOperationResult | null\n  /** Execute a lock */\n  lock: (params: LockParams) => Promise<void>\n  /** Execute an unlock */\n  unlock: () => Promise<void>\n  /** Clear error and last result */\n  reset: () => void\n}\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\nconst EMPTY_LOCK_DATA: LockData = {\n  totalLocked: 0,\n  unlockable: 0,\n  nextUnlock: 0,\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Manages time-lock BSV state and operations.\n *\n * Accepts `onLock` and `onUnlock` callbacks that should be wired to\n * `@1sat/actions` `lockBsv.execute` and `unlockBsv.execute` respectively.\n *\n * @example\n * ```ts\n * const { lockData, isLocking, lock, unlock } = useLockBsv({\n *   onLock: async (params) => lockBsv.execute(ctx, { requests: [params] }),\n *   onUnlock: async () => unlockBsv.execute(ctx),\n * })\n * ```\n */\nexport function useLockBsv({\n  lockData: initialLockData,\n  onSuccess,\n  onError,\n  onLock,\n  onUnlock,\n}: UseLockBsvOptions = {}): UseLockBsvReturn {\n  const [lockData, setLockData] = useState<LockData>(\n    initialLockData ?? EMPTY_LOCK_DATA\n  )\n  const [isLocking, setIsLocking] = useState(false)\n  const [isUnlocking, setIsUnlocking] = useState(false)\n  const [error, setError] = useState<Error | null>(null)\n  const [lastResult, setLastResult] = useState<LockOperationResult | null>(null)\n\n  const lock = useCallback(\n    async (params: LockParams) => {\n      if (!onLock) {\n        const err = new Error(\"onLock callback is not configured\")\n        setError(err)\n        onError?.(err)\n        return\n      }\n\n      setIsLocking(true)\n      setError(null)\n\n      try {\n        const result = await onLock(params)\n\n        if (result.error) {\n          const err = new Error(result.error)\n          setError(err)\n          onError?.(err)\n          return\n        }\n\n        setLastResult(result)\n        // Optimistically update lock data\n        setLockData((prev) => ({\n          ...prev,\n          totalLocked: prev.totalLocked + params.satoshis,\n          nextUnlock:\n            prev.nextUnlock === 0\n              ? params.until\n              : Math.min(prev.nextUnlock, params.until),\n        }))\n        onSuccess?.(result)\n      } catch (err) {\n        const e = err instanceof Error ? err : new Error(String(err))\n        setError(e)\n        onError?.(e)\n      } finally {\n        setIsLocking(false)\n      }\n    },\n    [onLock, onSuccess, onError]\n  )\n\n  const unlock = useCallback(async () => {\n    if (!onUnlock) {\n      const err = new Error(\"onUnlock callback is not configured\")\n      setError(err)\n      onError?.(err)\n      return\n    }\n\n    setIsUnlocking(true)\n    setError(null)\n\n    try {\n      const result = await onUnlock()\n\n      if (result.error) {\n        const err = new Error(result.error)\n        setError(err)\n        onError?.(err)\n        return\n      }\n\n      setLastResult(result)\n      // Optimistically update lock data\n      setLockData((prev) => ({\n        ...prev,\n        totalLocked: prev.totalLocked - prev.unlockable,\n        unlockable: 0,\n      }))\n      onSuccess?.(result)\n    } catch (err) {\n      const e = err instanceof Error ? err : new Error(String(err))\n      setError(e)\n      onError?.(e)\n    } finally {\n      setIsUnlocking(false)\n    }\n  }, [onUnlock, onSuccess, onError])\n\n  const reset = useCallback(() => {\n    setError(null)\n    setLastResult(null)\n  }, [])\n\n  return {\n    lockData,\n    isLocking,\n    isUnlocking,\n    error,\n    lastResult,\n    lock,\n    unlock,\n    reset,\n  }\n}\n",
      "type": "registry:component",
      "target": "~/components/blocks/lock-bsv/use-lock-bsv.ts"
    }
  ],
  "categories": [
    "wallet"
  ],
  "type": "registry:block"
}