{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "post-provider",
  "type": "registry:lib",
  "title": "Lens Post Provider",
  "description": "A provider of a shared Lens post context for state, queries, and operations.",
  "dependencies": [
    "@lens-protocol/react@0.0.0-canary-20250820102520",
    "@lens-protocol/client@0.0.0-canary-20250820102520",
    "@lens-protocol/metadata@2.1.0",
    "@lens-chain/storage-client@1.0.6",
    "@lens-chain/sdk@1.0.3",
    "viem@2.33.0"
  ],
  "registryDependencies": ["https://lensblocks.com/r/use-reaction-toggle.json", "https://lensblocks.com/r/utils.json"],
  "files": [
    {
      "path": "registry/new-york/lib/lens-post-provider.tsx",
      "content": "import { createContext, ReactNode, useEffect, useMemo, useState } from \"react\";\nimport {\n  AnyPost,\n  CreatePostRequest,\n  EvmAddress,\n  evmAddress,\n  PaymentSource,\n  Post,\n  postId as toPostId,\n  SessionClient,\n  TxHash,\n  useBookmarkPost,\n  usePost,\n  useUndoBookmarkPost,\n} from \"@lens-protocol/react\";\nimport { executePostAction, post as createPost, repost as createRepost } from \"@lens-protocol/client/actions\";\nimport { handleOperationWith } from \"@lens-protocol/client/viem\";\nimport { useReactionToggle } from \"@/registry/new-york/hooks/use-reaction-toggle\";\nimport { LensChainNativeToken } from \"@/registry/new-york/lib/lens-utils\";\nimport { textOnly } from \"@lens-protocol/metadata\";\nimport { immutable, StorageClient } from \"@lens-chain/storage-client\";\nimport { chains } from \"@lens-chain/sdk/viem\";\nimport { WalletClient } from \"viem\";\nimport { Result } from \"@/registry/new-york/lib/result\";\n\nexport type OptimisticState = {\n  liked: boolean;\n  commented: boolean;\n  collected: boolean;\n  repostedOrQuoted: boolean;\n  tipped: boolean;\n  bookmarked: boolean;\n  likeCount: number;\n  commentCount: number;\n  collectCount: number;\n  repostAndQuoteCount: number;\n  tipCount: number;\n};\n\nexport type Referral = {\n  percent: number;\n  address: EvmAddress;\n};\n\ntype PostContextType = {\n  sessionClient?: SessionClient | null | undefined;\n  post: AnyPost | undefined | null;\n  loading: boolean;\n  error?: Error;\n  optimistic: OptimisticState;\n  toggleLike: () => Promise<boolean>;\n  comment: (content: string) => Promise<TxHash>;\n  collect: (paymentSource?: PaymentSource, referrals?: Referral[]) => Promise<TxHash>;\n  repost: () => Promise<TxHash | undefined>;\n  quote: (content: string) => Promise<TxHash>;\n  tip: (paymentSource: PaymentSource, amount: string, tokenAddress: string) => Promise<TxHash>;\n  toggleBookmark: () => Promise<boolean>;\n};\n\nexport const LensPostContext = createContext<PostContextType | undefined>(undefined);\n\ntype Props = {\n  /**\n   * The Lens Session Client used for making authenticated calls\n   */\n  session: Result<SessionClient>;\n\n  /**\n   * The wallet client from viem used to sign messages for authentication.\n   */\n  wallet?: { data: WalletClient | undefined | null; isLoading?: boolean; error?: unknown };\n\n  postId: string;\n\n  children: ReactNode;\n\n  useTestnet?: boolean;\n};\n\nexport const LensPostProvider = ({ session, wallet, postId, children, useTestnet = false }: Props) => {\n  const sessionClient = session?.data;\n  const walletClient = wallet?.data;\n\n  const { data, loading, error } = usePost({ post: postId });\n\n  const [post, setPost] = useState<Post | undefined | null>(data?.__typename === \"Repost\" ? data.repostOf : data);\n\n  const operations = post && \"operations\" in post ? post.operations : null;\n  const stats = post && \"stats\" in post ? post.stats : null;\n\n  const defaultOptimisticState: OptimisticState = useMemo(\n    () => ({\n      liked: operations?.hasUpvoted ?? false,\n      bookmarked: operations?.hasBookmarked ?? false,\n      collected: operations?.hasSimpleCollected ?? false,\n      tipped: operations?.hasTipped ?? false,\n      commented: operations?.hasCommented.optimistic ?? operations?.hasCommented.onChain ?? false,\n      repostedOrQuoted:\n        operations?.hasReposted.optimistic ??\n        operations?.hasReposted.onChain ??\n        operations?.hasQuoted.optimistic ??\n        operations?.hasQuoted.onChain ??\n        false,\n      likeCount: stats?.upvotes ?? 0,\n      commentCount: stats?.comments ?? 0,\n      collectCount: stats?.collects ?? 0,\n      repostAndQuoteCount: (stats?.reposts ?? 0) + (stats?.quotes ?? 0),\n      tipCount: stats?.tips ?? 0,\n    }),\n    [operations, stats],\n  );\n  const [optimistic, setOptimistic] = useState<OptimisticState>(defaultOptimisticState);\n\n  useEffect(() => {\n    setPost(data?.__typename === \"Repost\" ? data.repostOf : data);\n    setOptimistic(defaultOptimisticState);\n  }, [data, defaultOptimisticState]);\n\n  const storageClient = StorageClient.create();\n  const { execute: toggleReaction } = useReactionToggle();\n  const { execute: bookmarkPost } = useBookmarkPost();\n  const { execute: undoBookmarkPost } = useUndoBookmarkPost();\n\n  const chain = useTestnet ? chains.testnet : chains.mainnet;\n\n  const switchChain = () => walletClient?.switchChain({ id: chain.id });\n\n  const toggleLike = async () => {\n    if (!post || !sessionClient?.isSessionClient()) return false;\n\n    setOptimistic(optimistic => ({\n      ...optimistic,\n      liked: !optimistic?.liked,\n      likeCount: optimistic.liked ? Math.max(0, optimistic.likeCount - 1) : optimistic.likeCount + 1,\n    }));\n\n    const res = await toggleReaction({ post, session: sessionClient });\n\n    if (res.isErr()) {\n      setOptimistic(optimistic => ({\n        ...optimistic,\n        liked: !optimistic.liked,\n        likeCount: optimistic.liked ? Math.max(0, optimistic.likeCount - 1) : optimistic.likeCount + 1,\n      }));\n      throw res.error;\n    }\n\n    return !optimistic.liked;\n  };\n\n  const comment = async (content: string): Promise<TxHash> => {\n    if (!post) {\n      throw new Error(\"Cannot comment without a post\");\n    }\n\n    if (!sessionClient?.isSessionClient() || !walletClient) {\n      throw new Error(\"Must be logged in to comment on a post\");\n    }\n\n    await switchChain();\n\n    const metadata = textOnly({ content });\n    const acl = immutable(chain.id);\n    const { uri } = await storageClient.uploadAsJson(metadata, { acl });\n    const postRequest: CreatePostRequest = {\n      contentUri: uri,\n      commentOn: {\n        post: post.id,\n      },\n    };\n\n    const previouslyCommented = optimistic.commented;\n\n    setOptimistic(optimistic => ({\n      ...optimistic,\n      commented: true,\n    }));\n\n    const postResponse = await createPost(sessionClient, postRequest)\n      .andThen(handleOperationWith(walletClient))\n      .andThen(sessionClient.waitForTransaction);\n\n    if (postResponse.isErr()) {\n      setOptimistic(optimistic => ({\n        ...optimistic,\n        commented: previouslyCommented,\n      }));\n      throw postResponse.error;\n    }\n\n    return postResponse.value;\n  };\n\n  const collect = async (paymentSource?: PaymentSource, referrals?: Referral[]): Promise<TxHash> => {\n    if (!post) {\n      throw new Error(\"Cannot collect without a post\");\n    }\n\n    if (!sessionClient?.isSessionClient() || !walletClient) {\n      throw new Error(\"Must be logged in to collect a post\");\n    }\n\n    await switchChain();\n\n    setOptimistic(optimistic => ({\n      ...optimistic,\n      collected: true,\n    }));\n\n    const res = await executePostAction(sessionClient, {\n      post: post.id,\n      action: {\n        simpleCollect: {\n          selected: true,\n          paymentSource,\n          referrals,\n        },\n      },\n    })\n      .andThen(handleOperationWith(walletClient))\n      .andThen(sessionClient.waitForTransaction);\n\n    if (res.isErr()) {\n      setOptimistic(optimistic => ({\n        ...optimistic,\n        collected: false,\n        collectCount: Math.max(0, optimistic.collectCount - 1),\n      }));\n      throw res.error;\n    }\n\n    setOptimistic(optimistic => ({\n      ...optimistic,\n      collectCount: optimistic.collectCount + 1,\n    }));\n\n    return res.value;\n  };\n\n  const repost = async (): Promise<TxHash | undefined> => {\n    if (!sessionClient?.isSessionClient() || !walletClient) {\n      throw new Error(\"Must be logged in to repost\");\n    }\n\n    await switchChain();\n\n    const previouslyReposted = optimistic.repostedOrQuoted;\n\n    setOptimistic(optimistic => ({\n      ...optimistic,\n      repostedOrQuoted: true,\n      repostAndQuoteCount: optimistic.repostAndQuoteCount + 1,\n    }));\n\n    const res = await createRepost(sessionClient, {\n      post: toPostId(postId),\n    })\n      .andThen(handleOperationWith(walletClient))\n      .andThen(sessionClient.waitForTransaction);\n\n    if (res.isErr()) {\n      setOptimistic(optimistic => ({\n        ...optimistic,\n        repostedOrQuoted: previouslyReposted,\n        repostAndQuoteCount: Math.max(0, optimistic.repostAndQuoteCount - 1),\n      }));\n      throw res.error;\n    }\n\n    return res.value;\n  };\n\n  const quote = async (content: string): Promise<TxHash> => {\n    if (!post) {\n      throw new Error(\"Cannot quote without a post\");\n    }\n\n    if (!sessionClient?.isSessionClient() || !walletClient) {\n      throw new Error(\"Must be logged in to quote a post\");\n    }\n\n    await switchChain();\n\n    const metadata = textOnly({ content });\n    const acl = immutable(chain.id);\n    const { uri } = await storageClient.uploadAsJson(metadata, { acl });\n    const postRequest: CreatePostRequest = {\n      contentUri: uri,\n      quoteOf: {\n        post: post.id,\n      },\n    };\n\n    const previouslyQuoted = optimistic.repostedOrQuoted;\n\n    setOptimistic(optimistic => ({\n      ...optimistic,\n      repostedOrQuoted: true,\n      repostAndQuoteCount: optimistic.repostAndQuoteCount + 1,\n    }));\n\n    const postResponse = await createPost(sessionClient, postRequest)\n      .andThen(handleOperationWith(walletClient))\n      .andThen(sessionClient.waitForTransaction);\n\n    if (postResponse.isErr()) {\n      setOptimistic(optimistic => ({\n        ...optimistic,\n        repostedOrQuoted: previouslyQuoted,\n        repostAndQuoteCount: Math.max(0, optimistic.repostAndQuoteCount - 1),\n      }));\n      throw postResponse.error;\n    }\n\n    return postResponse.value;\n  };\n\n  const tip = async (paymentSource: PaymentSource, amount: string, tokenAddress: string): Promise<TxHash> => {\n    if (!post) {\n      throw new Error(\"Cannot tip without a post\");\n    }\n\n    if (!sessionClient?.isSessionClient() || !walletClient) {\n      throw new Error(\"Must be logged in to tip\");\n    }\n\n    await switchChain();\n\n    setOptimistic(optimistic => ({\n      ...optimistic,\n      tipped: true,\n    }));\n\n    const res = await executePostAction(sessionClient, {\n      post: post.id,\n      action: {\n        tipping: {\n          paymentSource,\n          ...(tokenAddress === LensChainNativeToken\n            ? { native: amount }\n            : { value: amount, token: evmAddress(tokenAddress) }),\n        },\n      },\n    })\n      .andThen(handleOperationWith(walletClient))\n      .andThen(sessionClient.waitForTransaction);\n\n    if (res.isErr()) {\n      setOptimistic(optimistic => ({\n        ...optimistic,\n        tipped: false,\n        tipCount: Math.max(0, optimistic.tipCount - 1),\n      }));\n      throw res.error;\n    }\n\n    setOptimistic(optimistic => ({\n      ...optimistic,\n      tipCount: optimistic.tipCount + 1,\n    }));\n\n    return res.value;\n  };\n\n  const toggleBookmark = async () => {\n    if (!postId) return false;\n\n    setOptimistic(optimistic => ({\n      ...optimistic,\n      bookmarked: !optimistic.bookmarked,\n    }));\n\n    if (operations?.hasBookmarked) {\n      await undoBookmarkPost({ post: toPostId(postId) });\n    } else {\n      await bookmarkPost({ post: toPostId(postId) });\n    }\n\n    return !optimistic.bookmarked;\n  };\n\n  return (\n    <LensPostContext.Provider\n      value={{\n        sessionClient,\n        post,\n        toggleLike,\n        comment,\n        collect,\n        repost,\n        quote,\n        tip,\n        toggleBookmark,\n        loading,\n        error,\n        optimistic,\n      }}\n    >\n      {children}\n    </LensPostContext.Provider>\n  );\n};\n",
      "type": "registry:lib"
    },
    {
      "path": "registry/new-york/lib/result.ts",
      "content": "export type Result<T, E = never> = {\n  data: T | null | undefined;\n  error: E | null | undefined;\n  loading: boolean;\n};\n\nexport const isResult = <T, E = never>(result: any): result is Result<T, E> => {\n  return \"data\" in result && \"error\" in result && \"loading\" in result;\n};\n\nexport const isLoading = (result: any): boolean => {\n  if (isResult(result)) {\n    return result.loading;\n  }\n  return false;\n};\n",
      "type": "registry:lib"
    }
  ]
}
