2024-10-25 21:12:53 +05:30
|
|
|
import React, { useState, useEffect } from 'react';
|
2024-10-25 05:41:04 +05:30
|
|
|
import { useAccount, useSignMessage, useEnsName } from 'wagmi';
|
|
|
|
|
import type { LightNode } from '@waku/interfaces';
|
|
|
|
|
import { useWaku } from '@waku/react';
|
|
|
|
|
import { createMessage, encoder, BlockPayload } from '@/lib/waku';
|
|
|
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
import { Loader2 } from "lucide-react";
|
|
|
|
|
import QRCode from '@/components/QRCode';
|
|
|
|
|
import { v4 as uuidv4 } from 'uuid';
|
2024-10-29 14:56:58 +05:30
|
|
|
import { useWalletPrompt } from '@/hooks/useWalletPrompt';
|
2024-10-25 05:41:04 +05:30
|
|
|
|
|
|
|
|
interface SignChainProps {
|
|
|
|
|
block: BlockPayload;
|
2024-10-28 18:23:21 +05:30
|
|
|
chainsData: BlockPayload[]; // Add this prop
|
2024-10-25 05:41:04 +05:30
|
|
|
onSuccess: (newBlock: BlockPayload) => void;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-28 18:23:21 +05:30
|
|
|
const SignChain: React.FC<SignChainProps> = ({ block, chainsData, onSuccess }) => {
|
2024-10-25 05:41:04 +05:30
|
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
|
const [isSigning, setIsSigning] = useState(false);
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
2024-10-25 21:12:53 +05:30
|
|
|
const [alreadySigned, setAlreadySigned] = useState(false);
|
2024-10-25 05:41:04 +05:30
|
|
|
const { address } = useAccount();
|
|
|
|
|
const { data: ensName } = useEnsName({ address });
|
|
|
|
|
const { node } = useWaku<LightNode>();
|
2024-10-29 14:56:58 +05:30
|
|
|
const { ensureWalletConnected } = useWalletPrompt();
|
2024-10-25 21:12:53 +05:30
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (address) {
|
2024-10-28 18:23:21 +05:30
|
|
|
// Check if the address has signed this block or any blocks in the chain
|
|
|
|
|
const checkSignatures = (blockToCheck: BlockPayload): boolean => {
|
|
|
|
|
// Check current block's signatures
|
|
|
|
|
if (blockToCheck.signatures.some(
|
|
|
|
|
sig => sig.address.toLowerCase() === address.toLowerCase()
|
|
|
|
|
)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check parent blocks
|
|
|
|
|
const parentBlock = chainsData.find(b => b.blockUUID === blockToCheck.parentBlockUUID);
|
|
|
|
|
if (parentBlock && checkSignatures(parentBlock)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check child blocks
|
|
|
|
|
const childBlocks = chainsData.filter(b => b.parentBlockUUID === blockToCheck.blockUUID);
|
|
|
|
|
return childBlocks.some(childBlock => checkSignatures(childBlock));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const hasAlreadySigned = checkSignatures(block);
|
2024-10-25 21:12:53 +05:30
|
|
|
setAlreadySigned(hasAlreadySigned);
|
|
|
|
|
}
|
2024-10-28 18:23:21 +05:30
|
|
|
}, [address, block, chainsData]);
|
2024-10-25 21:12:53 +05:30
|
|
|
|
2024-10-25 05:41:04 +05:30
|
|
|
const { signMessage } = useSignMessage({
|
|
|
|
|
mutation: {
|
2024-11-04 21:57:12 +05:30
|
|
|
onMutate() {
|
|
|
|
|
// Reset any previous errors when starting a new signing attempt
|
|
|
|
|
setError(null);
|
|
|
|
|
setIsSigning(true);
|
|
|
|
|
},
|
2024-10-25 05:41:04 +05:30
|
|
|
async onSuccess(signature) {
|
|
|
|
|
if (!address || !node) return;
|
|
|
|
|
|
2024-11-04 21:57:12 +05:30
|
|
|
try {
|
|
|
|
|
// Double check signature before proceeding
|
|
|
|
|
if (block.signatures.some(sig => sig.address.toLowerCase() === address.toLowerCase())) {
|
|
|
|
|
setError('You have already signed this chain.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-10-25 21:12:53 +05:30
|
|
|
|
2024-11-04 21:57:12 +05:30
|
|
|
const newBlock: BlockPayload = {
|
|
|
|
|
chainUUID: block.chainUUID,
|
|
|
|
|
blockUUID: uuidv4(),
|
|
|
|
|
title: block.title,
|
|
|
|
|
description: block.description,
|
|
|
|
|
signedMessage: signature,
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
signatures: [{ address, signature }],
|
|
|
|
|
parentBlockUUID: block.blockUUID
|
|
|
|
|
};
|
2024-10-25 05:41:04 +05:30
|
|
|
|
|
|
|
|
const wakuMessage = createMessage(newBlock);
|
|
|
|
|
const { failures, successes } = await node.lightPush.send(encoder, wakuMessage);
|
|
|
|
|
|
|
|
|
|
if (failures.length > 0 || successes.length === 0) {
|
|
|
|
|
throw new Error('Failed to send message to Waku network');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onSuccess(newBlock);
|
|
|
|
|
setIsOpen(false);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error creating new block:', error);
|
|
|
|
|
setError('Failed to create new block. Please try again.');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
onError(error) {
|
|
|
|
|
console.error('Error signing message:', error);
|
2024-11-04 21:57:12 +05:30
|
|
|
setError('Error signing message. Please try again. If using a mobile wallet, please ensure your wallet app is open.');
|
|
|
|
|
},
|
|
|
|
|
onSettled() {
|
2024-10-25 05:41:04 +05:30
|
|
|
setIsSigning(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-11-04 21:57:12 +05:30
|
|
|
const handleSign = async () => {
|
|
|
|
|
try {
|
|
|
|
|
if (!address) {
|
|
|
|
|
// If not connected, try to connect first
|
|
|
|
|
const connected = await ensureWalletConnected();
|
|
|
|
|
if (!connected) return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if already signed
|
|
|
|
|
if (alreadySigned) {
|
|
|
|
|
setError('You have already signed this chain.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prepare the message
|
|
|
|
|
const message = `Sign Block:
|
|
|
|
|
Chain UUID: ${block.chainUUID}
|
|
|
|
|
Block UUID: ${block.blockUUID}
|
|
|
|
|
Title: ${block.title}
|
|
|
|
|
Description: ${block.description}
|
|
|
|
|
Timestamp: ${new Date().getTime()}
|
|
|
|
|
Parent Block UUID: ${block.parentBlockUUID}
|
|
|
|
|
Signed by: ${ensName || address}`;
|
|
|
|
|
|
|
|
|
|
// Trigger signing
|
|
|
|
|
signMessage({ message });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error in sign flow:', error);
|
|
|
|
|
setError('Failed to initiate signing. Please try again.');
|
|
|
|
|
setIsSigning(false);
|
2024-10-25 21:12:53 +05:30
|
|
|
}
|
2024-10-25 05:41:04 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
2024-10-25 21:12:53 +05:30
|
|
|
<Button onClick={() => setIsOpen(true)} disabled={alreadySigned}>
|
2024-11-04 21:57:12 +05:30
|
|
|
{alreadySigned ? 'Already Signed' : !address ? 'Connect Wallet' : 'Sign Chain'}
|
2024-10-25 21:12:53 +05:30
|
|
|
</Button>
|
2024-10-25 05:41:04 +05:30
|
|
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
2024-10-28 18:23:21 +05:30
|
|
|
<DialogContent className="sm:max-w-md">
|
2024-10-25 05:41:04 +05:30
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle>Sign Chain</DialogTitle>
|
|
|
|
|
<DialogDescription>
|
2024-10-25 21:12:53 +05:30
|
|
|
{alreadySigned
|
|
|
|
|
? 'You have already signed this chain.'
|
|
|
|
|
: 'Review the block details and sign to add your signature to the chain.'}
|
2024-10-25 05:41:04 +05:30
|
|
|
</DialogDescription>
|
|
|
|
|
</DialogHeader>
|
2024-10-28 18:23:21 +05:30
|
|
|
<div className="flex flex-col space-y-4">
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<h4 className="font-medium">Block Details</h4>
|
|
|
|
|
<p className="text-sm text-muted-foreground">{block.title}</p>
|
|
|
|
|
<p className="text-sm text-muted-foreground">{block.description}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<QRCode text={`${window.location.origin}/sign/${block.chainUUID}/${block.blockUUID}`} />
|
|
|
|
|
</div>
|
2024-10-25 05:41:04 +05:30
|
|
|
{error && <p className="text-sm text-destructive">{error}</p>}
|
|
|
|
|
<DialogFooter>
|
|
|
|
|
<Button variant="secondary" onClick={() => setIsOpen(false)}>Cancel</Button>
|
2024-10-25 21:12:53 +05:30
|
|
|
<Button onClick={handleSign} disabled={isSigning || alreadySigned}>
|
2024-10-25 05:41:04 +05:30
|
|
|
{isSigning ? (
|
|
|
|
|
<>
|
|
|
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
|
|
|
Signing...
|
|
|
|
|
</>
|
2024-10-25 21:12:53 +05:30
|
|
|
) : alreadySigned ? (
|
|
|
|
|
'Already Signed'
|
2024-11-04 21:57:12 +05:30
|
|
|
) : !address ? (
|
|
|
|
|
'Connect Wallet'
|
2024-10-25 05:41:04 +05:30
|
|
|
) : (
|
|
|
|
|
'Sign'
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
|
|
|
|
</DialogFooter>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default SignChain;
|