Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/api/moment/comments/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const queryParams = {
moment: {
contractAddress: searchParams.get("contractAddress") as Address,
collectionAddress: searchParams.get("collectionAddress") as Address,
tokenId: searchParams.get("tokenId") || "1",
},
chainId: Number(searchParams.get("chainId")) || CHAIN_ID,
Expand Down
7 changes: 5 additions & 2 deletions app/api/moment/update-uri/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ export async function POST(req: NextRequest) {
}
const data = parseResult.data;
const result = await updateMomentURI({
tokenContractAddress: data.moment.contractAddress as Address,
tokenId: data.moment.tokenId,
moment: {
collectionAddress: data.moment.collectionAddress as Address,
tokenId: data.moment.tokenId,
chainId: data.moment.chainId,
},
newUri: data.newUri,
artistAddress: artistAddress as Address,
});
Expand Down
7 changes: 5 additions & 2 deletions components/CreatedMoment/CreatedMomentAirdrop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ const CreatedMomentAirdrop = () => {

return (
<MomentProvider
token={{ tokenContractAddress: createdContract as Address, tokenId: createdTokenId }}
chainId={CHAIN_ID}
moment={{
collectionAddress: createdContract as Address,
tokenId: createdTokenId,
chainId: CHAIN_ID,
}}
>
<MomentAirdrop />
</MomentProvider>
Expand Down
6 changes: 3 additions & 3 deletions components/TokenManagePage/TokenManagePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ const TokenManagePage = () => {

return (
<MomentProvider
token={{
tokenContractAddress: collection.address,
moment={{
collectionAddress: collection.address,
tokenId,
chainId: collection.chainId,
}}
chainId={collection.chainId}
>
<TokenOverview />
<ManageTabs
Expand Down
6 changes: 3 additions & 3 deletions components/TokenPage/TokenPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ const TokenPage = () => {
const viemChainName = ZORA_TO_VIEM[chain as ZoraChains];
const viemChain = chains[viemChainName];

const token = {
tokenContractAddress: address as Address,
const moment = {
collectionAddress: address as Address,
tokenId,
chainId: viemChain.id,
};

return (
<main className="w-screen flex grow">
<div className="w-full flex flex-col items-center justify-center pt-12 md:pt-14">
<MomentProvider token={token} chainId={viemChain.id}>
<MomentProvider moment={moment}>
<MomentCommentsProvider>
<MomentCollectProvider>
<Token />
Expand Down
7 changes: 3 additions & 4 deletions hooks/useAirdrop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export interface AirdropItem {
ensName: string;
}
const useAirdrop = () => {
const { token } = useMomentProvider();
const { tokenContractAddress: momentContract, tokenId } = token;
const { moment } = useMomentProvider();
const { collectionAddress, tokenId } = moment;
const [airdopToItems, setAirdropToItems] = useState<AirdropItem[]>([]);
const { artistWallet, isPrepared } = useUserProvider();
const { smartWallet } = useSmartWalletProvider();
Expand Down Expand Up @@ -66,8 +66,7 @@ const useAirdrop = () => {

const hash = await executeAirdrop({
airdropToItems: airdopToItems,
tokenId,
momentContract,
moment,
smartWallet: smartWallet as Address,
artistWallet: artistWallet as Address,
accessToken,
Expand Down
10 changes: 5 additions & 5 deletions hooks/useBalanceOf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ import { Address } from "viem";

const useBalanceOf = () => {
const [balanceOf, setBalanceOf] = useState<number>(0);
const { token } = useMomentProvider();
const { moment } = useMomentProvider();
const { connectedAddress } = useUserProvider();

useEffect(() => {
const getBalanceOf = async () => {
const publicClient = getPublicClient();
const response = await publicClient.readContract({
address: token.tokenContractAddress as Address,
address: moment.collectionAddress as Address,
abi: zoraCreator1155ImplABI,
functionName: "balanceOf",
args: [connectedAddress as Address, BigInt(token.tokenId)],
args: [connectedAddress as Address, BigInt(moment.tokenId)],
});

setBalanceOf(parseInt(response.toString(), 10));
};
if (token && connectedAddress) getBalanceOf();
}, [token, connectedAddress]);
if (moment && connectedAddress) getBalanceOf();
}, [moment, connectedAddress]);

return {
balanceOf,
Expand Down
18 changes: 7 additions & 11 deletions hooks/useComments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,18 @@ import { useMomentProvider } from "@/providers/MomentProvider";
const COMMENTS_PER_PAGE = 20;

export function useComments() {
const { token } = useMomentProvider();
const { moment } = useMomentProvider();
const queryClient = useQueryClient();
const { tokenContractAddress: contractAddress, tokenId, chainId } = token;
const { collectionAddress, tokenId, chainId } = moment;

const query = useInfiniteQuery({
queryKey: ["comments", contractAddress, tokenId, chainId],
queryKey: ["comments", collectionAddress, tokenId, chainId],
queryFn: ({ pageParam = 0 }) =>
fetchComments({
moment: {
contractAddress: contractAddress!,
tokenId: tokenId!,
},
chainId: chainId!,
moment,
offset: pageParam as number,
}),
enabled: Boolean(contractAddress && tokenId && chainId),
enabled: Boolean(moment),
staleTime: 1000 * 60 * 5, // 5 minutes
retry: (failureCount) => failureCount < 3,
getNextPageParam: (lastPage, allPages) => {
Expand All @@ -48,7 +44,7 @@ export function useComments() {
(comment: MintComment) => {
// Optimistically update the query cache
queryClient.setQueryData<InfiniteData<MintComment[], number>>(
["comments", contractAddress, tokenId, chainId],
["comments", collectionAddress, tokenId, chainId],
(oldData) => {
if (!oldData) {
return {
Expand All @@ -64,7 +60,7 @@ export function useComments() {
}
);
},
[queryClient, contractAddress, tokenId, chainId]
[queryClient, collectionAddress, tokenId, chainId]
);

const fetchMore = useCallback(() => {
Expand Down
16 changes: 4 additions & 12 deletions hooks/useMomentCollect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const useMomentCollect = () => {
const [collected, setCollected] = useState(false);
const { artistWallet } = useUserProvider();
const [isLoading, setIsLoading] = useState(false);
const { token, saleConfig } = useMomentProvider();
const { moment, saleConfig } = useMomentProvider();
const { comment, addComment, setComment, setIsOpenCommentModal } = useMomentCommentsProvider();
const { validateBalance } = useCollectBalanceValidation();
const { getAccessToken } = usePrivy();
Expand All @@ -50,12 +50,12 @@ const useMomentCollect = () => {

if (saleConfig.pricePerToken === BigInt(0)) {
await mintOnSmartWallet({
address: token.tokenContractAddress,
address: moment.collectionAddress,
abi: zoraCreator1155ImplABI,
functionName: "mint",
args: [
zoraCreatorFixedPriceSaleStrategyAddress[CHAIN.id],
token.tokenId,
moment.tokenId,
amountToCollect,
[],
minterArguments,
Expand All @@ -67,15 +67,7 @@ const useMomentCollect = () => {
if (!accessToken) {
throw new Error("Failed to get access token");
}
await collectMomentApi(
{
contractAddress: token.tokenContractAddress,
tokenId: token.tokenId,
},
amountToCollect,
comment,
accessToken
);
await collectMomentApi(moment, amountToCollect, comment, accessToken);
}
addComment({
sender: artistWallet as Address,
Expand Down
17 changes: 11 additions & 6 deletions hooks/useSaleConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import { zoraCreatorFixedPriceSaleStrategyAddress } from "@/lib/protocolSdk/cons
import { useUserProvider } from "@/providers/UserProvider";

const useSaleConfig = () => {
const { saleConfig: sale } = useMomentProvider();
const { saleConfig: sale, moment, fetchTokenInfo } = useMomentProvider();
const [saleStart, setSaleStart] = useState<Date>(new Date());
const { token, fetchTokenInfo } = useMomentProvider();
const { signTransaction } = useSignTransaction();
const { connectedAddress } = useUserProvider();
const [isLoading, setIsLoading] = useState<boolean>(false);
Expand All @@ -29,14 +28,20 @@ const useSaleConfig = () => {
const calldata = encodeFunctionData({
abi: zoraCreatorFixedPriceSaleStrategyABI,
functionName: "setSale",
args: [BigInt(token.tokenId), newSale],
args: [BigInt(moment.tokenId), newSale],
});
const publicClient = getPublicClient(CHAIN_ID);
const publicClient = getPublicClient(moment.chainId || CHAIN_ID);
const hash = await signTransaction({
address: token.tokenContractAddress,
address: moment.collectionAddress,
abi: zoraCreator1155ImplABI,
functionName: "callSale",
args: [token.tokenId, zoraCreatorFixedPriceSaleStrategyAddress[CHAIN_ID], calldata],
args: [
moment.tokenId,
zoraCreatorFixedPriceSaleStrategyAddress[
moment.chainId as keyof typeof zoraCreatorFixedPriceSaleStrategyAddress
],
calldata,
],
account: connectedAddress as Address,
chain: CHAIN,
});
Comment on lines +33 to 47
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent chain usage between publicClient and transaction signing.

Line 33 derives the public client from moment.chainId || CHAIN_ID, but line 46 hardcodes chain: CHAIN. If moment.chainId differs from CHAIN_ID, the transaction will be signed for a different chain than the one used to wait for the receipt.

Consider deriving the chain consistently:

+import { getChain } from "@/lib/viem/getChain"; // or appropriate helper
+
     const publicClient = getPublicClient(moment.chainId || CHAIN_ID);
+    const targetChain = getChain(moment.chainId || CHAIN_ID);
     const hash = await signTransaction({
       // ...
       account: connectedAddress as Address,
-      chain: CHAIN,
+      chain: targetChain,
     });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In hooks/useSaleConfig.ts around lines 33 to 47, the code builds publicClient
using moment.chainId || CHAIN_ID but calls signTransaction with a hardcoded
chain: CHAIN, causing mismatched chains; fix by deriving a single chain
identifier (e.g. const activeChainId = moment.chainId || CHAIN_ID and map it to
the appropriate Chain value if needed), use that same activeChainId/Chain when
calling getPublicClient and pass the same derived Chain into signTransaction,
and ensure types align (cast or map numeric chainId to the expected Chain type)
so both public client and transaction signing use the identical chain.

Expand Down
4 changes: 2 additions & 2 deletions hooks/useShareMoment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { useMomentProvider } from "@/providers/MomentProvider";
import { toast } from "sonner";

const useShareMoment = () => {
const { token } = useMomentProvider();
const { moment } = useMomentProvider();

const share = async () => {
const shortNetworkName = getShortNetworkName(CHAIN.name.toLowerCase());
await navigator.clipboard.writeText(
`${SITE_ORIGINAL_URL}/collect/${shortNetworkName}:${token.tokenContractAddress}/${token.tokenId}`
`${SITE_ORIGINAL_URL}/collect/${shortNetworkName}:${moment.collectionAddress}/${moment.tokenId}`
);
toast.success("copied!");
};
Expand Down
9 changes: 5 additions & 4 deletions hooks/useTokenInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import { useUserProvider } from "@/providers/UserProvider";
import { getMomentApi } from "@/lib/moment/getMomentApi";
import { Moment } from "@/types/moment";

const useTokenInfo = (tokenContract: Address, tokenId: string, chainId: number) => {
const useTokenInfo = (moment: Moment) => {
const { artistWallet } = useUserProvider();

const query = useQuery({
queryKey: ["tokenInfo", tokenContract, tokenId, chainId],
queryFn: () => getMomentApi(tokenContract, tokenId, chainId),
enabled: Boolean(tokenContract && tokenId && chainId),
queryKey: ["tokenInfo", moment.collectionAddress, moment.tokenId, moment.chainId],
queryFn: () => getMomentApi(moment),
enabled: Boolean(moment.collectionAddress && moment.tokenId),
staleTime: 1000 * 60 * 5, // 5 minutes
retry: (failureCount) => failureCount < 3,
});
Expand Down
17 changes: 10 additions & 7 deletions hooks/useUpdateMomentURI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useMomentFormProvider } from "@/providers/MomentFormProvider";
import { migrateMuxToArweaveApi } from "@/lib/mux/migrateMuxToArweaveApi";

const useUpdateMomentURI = () => {
const { token, fetchTokenInfo } = useMomentProvider();
const { moment, fetchTokenInfo } = useMomentProvider();
const {
name: providerName,
description: providerDescription,
Expand All @@ -27,7 +27,11 @@ const useUpdateMomentURI = () => {
const updateTokenURI = async () => {
setIsLoading(true);
try {
const tokenInfo = await getTokenInfo(token.tokenContractAddress, token.tokenId, CHAIN_ID);
const tokenInfo = await getTokenInfo(
moment.collectionAddress,
moment.tokenId,
moment.chainId || CHAIN_ID
);
const current = await fetchTokenMetadata(tokenInfo.tokenUri);

const updatedAnimationUrl = animationUri || current?.animation_url;
Expand Down Expand Up @@ -56,7 +60,7 @@ const useUpdateMomentURI = () => {

const newUri = await uploadJson(updated);

if (!token?.tokenContractAddress || !token?.tokenId) {
if (!moment?.collectionAddress || !moment?.tokenId) {
throw new Error("Missing token context");
}

Expand All @@ -66,16 +70,15 @@ const useUpdateMomentURI = () => {
}

await callUpdateMomentURI({
tokenContractAddress: token.tokenContractAddress,
tokenId: token.tokenId,
moment,
newUri,
accessToken,
});

if (updatedMimeType?.includes("video")) {
await migrateMuxToArweaveApi({
tokenContractAddress: token.tokenContractAddress,
tokenId: token.tokenId,
tokenContractAddress: moment.collectionAddress,
tokenId: moment.tokenId,
accessToken,
});
resetForm();
Expand Down
8 changes: 4 additions & 4 deletions lib/moment/airdropMoment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface AirdropResult {
*/
export async function airdropMoment({
recipients,
momentContract,
collectionAddress,
artistAddress,
}: AirdropMomentInput): Promise<AirdropResult> {
// Get or create a smart account (contract wallet)
Expand All @@ -32,12 +32,12 @@ export async function airdropMoment({

// Check admin permission of artist wallet and smart wallet
const smartWalletPermissionBit = await getPermission(
momentContract as Address,
collectionAddress as Address,
smartAccount.address
);

if (smartWalletPermissionBit !== BigInt(PERMISSION_BIT_ADMIN)) {
const accountPermissionBit = await getPermission(momentContract as Address, artistAddress);
const accountPermissionBit = await getPermission(collectionAddress as Address, artistAddress);
if (accountPermissionBit !== BigInt(PERMISSION_BIT_ADMIN))
throw Error("The account does not have admin permission for this collection.");
else throw Error("Admin permission are not yet granted to smart wallet.");
Expand All @@ -64,7 +64,7 @@ export async function airdropMoment({
network: IS_TESTNET ? "base-sepolia" : "base",
calls: [
{
to: momentContract as Address,
to: collectionAddress as Address,
data: airdropCall,
},
],
Expand Down
13 changes: 5 additions & 8 deletions lib/moment/callUpdateMomentURI.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Moment } from "@/types/moment";

export interface CallUpdateMomentURIInput {
tokenContractAddress: string;
tokenId: string;
moment: Moment;
newUri: string;
accessToken: string;
}
Expand All @@ -10,8 +11,7 @@ export interface CallUpdateMomentURIInput {
* Handles authentication and error responses.
*/
export async function callUpdateMomentURI({
tokenContractAddress,
tokenId,
moment,
newUri,
accessToken,
}: CallUpdateMomentURIInput): Promise<void> {
Expand All @@ -23,10 +23,7 @@ export async function callUpdateMomentURI({
authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
moment: {
contractAddress: tokenContractAddress,
tokenId: tokenId,
},
moment,
newUri,
}),
});
Expand Down
Loading