From cca9a96c9a1c0dc842d08546bbeeeddd6dea5acf Mon Sep 17 00:00:00 2001 From: jipstavenuiter Date: Wed, 9 Jul 2025 13:08:14 -0400 Subject: [PATCH 1/2] feat(creator): add transfer restrictions label to hypercert component - Introduced TransferRestrictionsLabel component to display transfer restrictions for hypercerts. - Updated Creator component to include the new label, enhancing the information presented to users. --- components/hypercert/creator.tsx | 9 +- .../hypercert/transfer-restrictions-label.tsx | 107 ++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 components/hypercert/transfer-restrictions-label.tsx diff --git a/components/hypercert/creator.tsx b/components/hypercert/creator.tsx index 8376855..5f306e9 100644 --- a/components/hypercert/creator.tsx +++ b/components/hypercert/creator.tsx @@ -3,11 +3,12 @@ import { Cuboid } from "lucide-react"; import { SUPPORTED_CHAINS, SupportedChainIdType } from "@/configs/constants"; import CopyableHypercertId from "@/components/copyable-hypercert-id"; import { HypercertState } from "@/hypercerts/fragments/hypercert-state.fragment"; +import TransferRestrictionsLabel from "./transfer-restrictions-label"; export default function Creator({ hypercert }: { hypercert: HypercertState }) { if (!hypercert) return null; return ( -
+
{hypercert.hypercert_id && ( )} @@ -17,6 +18,12 @@ export default function Creator({ hypercert }: { hypercert: HypercertState }) {
)} +
+ +
{hypercert.contract?.chain_id && (
diff --git a/components/hypercert/transfer-restrictions-label.tsx b/components/hypercert/transfer-restrictions-label.tsx new file mode 100644 index 0000000..41d836f --- /dev/null +++ b/components/hypercert/transfer-restrictions-label.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { + parseClaimOrFractionId, + TransferRestrictions, +} from "@hypercerts-org/sdk"; +import { getAddress } from "viem"; +import { useReadContract } from "wagmi"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { useState } from "react"; + +export default function TransferRestrictionsLabel({ + hypercertId, + showSeparator = false, +}: { + hypercertId: string; + showSeparator?: boolean; +}) { + const [isOpen, setIsOpen] = useState(false); + const { contractAddress, id } = parseClaimOrFractionId(hypercertId); + const { data: transferRestrictions } = useReadContract({ + abi: [ + { + inputs: [{ internalType: "uint256", name: "tokenID", type: "uint256" }], + name: "readTransferRestriction", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + ], + address: getAddress(contractAddress || ""), + functionName: "readTransferRestriction", + args: [id], + query: { + enabled: !!contractAddress && !!id, + select: (data) => { + if (data === "AllowAll") { + return TransferRestrictions.AllowAll; + } else if (data === "DisallowAll") { + return TransferRestrictions.DisallowAll; + } else if (data === "FromCreatorOnly") { + return TransferRestrictions.FromCreatorOnly; + } + }, + }, + }); + if (transferRestrictions === undefined) return null; + return ( + <> + {showSeparator && } +
+ + + setIsOpen(true)} + onMouseLeave={() => setIsOpen(false)} + > + {getTransferRestrictionsText(transferRestrictions)} + + + +

+ {getTransferRestrictionsLabel(transferRestrictions)} +

+
+
+
+ + ); +} + +export const getTransferRestrictionsText = ( + transferRestrictions: TransferRestrictions, +) => { + switch (transferRestrictions) { + case TransferRestrictions.AllowAll: + return "Transferable"; + case TransferRestrictions.DisallowAll: + return "Not transferable"; + case TransferRestrictions.FromCreatorOnly: + return "Transferable-once"; + } +}; + +export const getTransferRestrictionsLabel = ( + transferRestrictions: TransferRestrictions, +) => { + switch (transferRestrictions) { + case TransferRestrictions.AllowAll: + return "Fractions can be transferred without limitations."; + case TransferRestrictions.DisallowAll: + return "Fractions can not be transferred"; + case TransferRestrictions.FromCreatorOnly: + return "Fractions can be transferred once from the creator to another user."; + } +}; From 858b89ee85652283d4593ebcd36dd330079f6095 Mon Sep 17 00:00:00 2001 From: jipstavenuiter Date: Wed, 9 Jul 2025 13:42:11 -0400 Subject: [PATCH 2/2] refactor: implement useReadTransferRestrictions hook for transfer restrictions - Replaced direct contract calls in TransferRestrictionsLabel and ListForSaleButton components with the new useReadTransferRestrictions hook for improved code reusability and clarity. - Updated components to handle transfer restrictions using the new hook, streamlining the logic and enhancing maintainability. --- .../hypercert/transfer-restrictions-label.tsx | 36 ++------------- .../marketplace/list-for-sale-button.tsx | 36 ++++----------- hooks/use-read-transfer-restrictions.ts | 44 +++++++++++++++++++ 3 files changed, 56 insertions(+), 60 deletions(-) create mode 100644 hooks/use-read-transfer-restrictions.ts diff --git a/components/hypercert/transfer-restrictions-label.tsx b/components/hypercert/transfer-restrictions-label.tsx index 41d836f..8c7ae53 100644 --- a/components/hypercert/transfer-restrictions-label.tsx +++ b/components/hypercert/transfer-restrictions-label.tsx @@ -12,6 +12,7 @@ import { PopoverTrigger, } from "@/components/ui/popover"; import { useState } from "react"; +import { useReadTransferRestrictions } from "@/hooks/use-read-transfer-restrictions"; export default function TransferRestrictionsLabel({ hypercertId, @@ -21,39 +22,8 @@ export default function TransferRestrictionsLabel({ showSeparator?: boolean; }) { const [isOpen, setIsOpen] = useState(false); - const { contractAddress, id } = parseClaimOrFractionId(hypercertId); - const { data: transferRestrictions } = useReadContract({ - abi: [ - { - inputs: [{ internalType: "uint256", name: "tokenID", type: "uint256" }], - name: "readTransferRestriction", - outputs: [ - { - internalType: "string", - name: "", - type: "string", - }, - ], - stateMutability: "view", - type: "function", - }, - ], - address: getAddress(contractAddress || ""), - functionName: "readTransferRestriction", - args: [id], - query: { - enabled: !!contractAddress && !!id, - select: (data) => { - if (data === "AllowAll") { - return TransferRestrictions.AllowAll; - } else if (data === "DisallowAll") { - return TransferRestrictions.DisallowAll; - } else if (data === "FromCreatorOnly") { - return TransferRestrictions.FromCreatorOnly; - } - }, - }, - }); + const transferRestrictions = useReadTransferRestrictions(hypercertId); + if (transferRestrictions === undefined) return null; return ( <> diff --git a/components/marketplace/list-for-sale-button.tsx b/components/marketplace/list-for-sale-button.tsx index e5c910d..96aa9ad 100644 --- a/components/marketplace/list-for-sale-button.tsx +++ b/components/marketplace/list-for-sale-button.tsx @@ -18,6 +18,8 @@ import { useState } from "react"; import { getAddress } from "viem"; import { isChainIdSupported } from "@/lib/isChainIdSupported"; +import { useReadTransferRestrictions } from "@/hooks/use-read-transfer-restrictions"; +import { TransferRestrictions } from "@hypercerts-org/sdk"; export function ListForSaleButton({ hypercert, @@ -34,29 +36,9 @@ export function ListForSaleButton({ const { chain_id: chainId, contract_address: contractAddress } = hypercert.contract || {}; - const { data: transferRestrictions } = useReadContract({ - abi: [ - { - inputs: [{ internalType: "uint256", name: "tokenID", type: "uint256" }], - name: "readTransferRestriction", - outputs: [ - { - internalType: "string", - name: "", - type: "string", - }, - ], - stateMutability: "view", - type: "function", - }, - ], - address: getAddress(contractAddress || ""), - functionName: "readTransferRestriction", - args: [tokenId!], - query: { - enabled: !!contractAddress && !!tokenId, - }, - }); + const transferRestrictions = useReadTransferRestrictions( + hypercert.hypercert_id || "", + ); const [isOpen, setIsOpen] = useState(false); @@ -87,8 +69,8 @@ export function ListForSaleButton({ !client || !client.isClaimOrFractionOnConnectedChain(hypercertId) || !fractionsOwnedByUser.length || - transferRestrictions === "DisallowAll" || - (transferRestrictions === "FromCreatorOnly" && + transferRestrictions === TransferRestrictions.DisallowAll || + (transferRestrictions === TransferRestrictions.FromCreatorOnly && address?.toLowerCase() !== hypercert.creator_address?.toLowerCase()); const getToolTipMessage = () => { @@ -114,11 +96,11 @@ export function ListForSaleButton({ return "You do not own any fractions of this hypercert"; } - if (transferRestrictions === "DisallowAll") { + if (transferRestrictions === TransferRestrictions.DisallowAll) { return "Secondary sales are not allowed for this hypercert"; } - if (transferRestrictions === "FromCreatorOnly") { + if (transferRestrictions === TransferRestrictions.FromCreatorOnly) { return "Only the creator can sell this hypercert"; } diff --git a/hooks/use-read-transfer-restrictions.ts b/hooks/use-read-transfer-restrictions.ts new file mode 100644 index 0000000..1e06af7 --- /dev/null +++ b/hooks/use-read-transfer-restrictions.ts @@ -0,0 +1,44 @@ +import { + parseClaimOrFractionId, + TransferRestrictions, +} from "@hypercerts-org/sdk"; +import { getAddress } from "viem"; +import { useReadContract } from "wagmi"; + +export const useReadTransferRestrictions = (hypercertId: string) => { + const { contractAddress, id } = parseClaimOrFractionId(hypercertId); + const { data: transferRestrictions } = useReadContract({ + abi: [ + { + inputs: [{ internalType: "uint256", name: "tokenID", type: "uint256" }], + name: "readTransferRestriction", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + ], + address: getAddress(contractAddress || ""), + functionName: "readTransferRestriction", + args: [id], + query: { + enabled: !!contractAddress && !!id, + select: (data) => { + if (data === "AllowAll") { + return TransferRestrictions.AllowAll; + } else if (data === "DisallowAll") { + return TransferRestrictions.DisallowAll; + } else if (data === "FromCreatorOnly") { + return TransferRestrictions.FromCreatorOnly; + } + }, + }, + }); + + return transferRestrictions; +};