Skip to content

Commit

Permalink
refactor: clean up /view page
Browse files Browse the repository at this point in the history
  • Loading branch information
ByteAtATime committed Dec 12, 2024
1 parent 1058e64 commit 019fa76
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 202 deletions.
32 changes: 32 additions & 0 deletions packages/nextjs/app/view/_components/SignatureMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
interface MessageSectionProps {
message: string | null;
typedData: any;
highlightedTypedData: string | null;
}

export const SignatureMessage: React.FC<MessageSectionProps> = ({ message, typedData, highlightedTypedData }) => {
return (
<div>
{message && (
<>
<h2 className="card-title">Message</h2>
<div className="bg-base-200 p-4 rounded-lg">
<p className="text-xs font-mono text-base-content break-all my-0">{message}</p>
</div>
</>
)}

{typedData && (
<>
<h2 className="card-title">Typed Data</h2>
{highlightedTypedData && (
<div
dangerouslySetInnerHTML={{ __html: highlightedTypedData }}
className="[&>pre]:p-4 [&>pre]:rounded-2xl [&>pre]:overflow-x-auto"
/>
)}
</>
)}
</div>
);
};
47 changes: 47 additions & 0 deletions packages/nextjs/app/view/_components/SignatureStatusIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { SignatureStatus } from "./types";
import { CheckCircleIcon, ExclamationCircleIcon } from "@heroicons/react/24/outline";

interface SignatureStatusIconProps {
status: SignatureStatus | undefined;
}

export const SignatureStatusIcon: React.FC<SignatureStatusIconProps> = ({ status }) => {
if (status === SignatureStatus.MATCH) {
return (
<div
className="p-1 rounded-full bg-success text-success-content tooltip before:max-w-48 float-start"
data-tip="The provided signature matches the address."
>
<CheckCircleIcon className="w-4 h-4" />
</div>
);
}

if (status === SignatureStatus.MISMATCH) {
return (
<div
className="p-1 rounded-full bg-error text-error-content tooltip before:max-w-48 float-start"
data-tip="The provided signature DOES NOT match the address."
>
<ExclamationCircleIcon className="w-4 h-4" />
</div>
);
}

if (!status) {
return (
<div className="p-1 rounded-full bg-warning text-warning-content tooltip h-6" data-tip="Verifying signature...">
<div className="loading loading-xs h-4" />
</div>
);
}

return (
<div
className="p-1 rounded-full bg-error text-error-content tooltip before:max-w-48 float-start"
data-tip="Something went wrong while verifying the signature. It might not be valid!"
>
<ExclamationCircleIcon className="w-4 h-4" />
</div>
);
};
31 changes: 31 additions & 0 deletions packages/nextjs/app/view/_components/SignaturesList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { SignatureStatusIcon } from "./SignatureStatusIcon";
import { SignatureStatus } from "./types";
import { Address } from "~~/components/scaffold-eth";

interface SignaturesListProps {
signatures: string[];
addresses: string[];
addressChecks: SignatureStatus[];
}

export const SignaturesList: React.FC<SignaturesListProps> = ({ signatures, addresses, addressChecks }) => {
return (
<div>
<h2 className="card-title">Signatures</h2>
{signatures.map((signature, index) => (
<div key={index} className="bg-base-200 p-4 rounded-lg">
<div className="flex items-center gap-2">
<Address address={addresses[index]} />
<div className="flex-grow" />
<div className="text-xs">
<SignatureStatusIcon status={addressChecks[index]} />
</div>
</div>
<div className="mt-2">
<p className="text-xs font-mono text-base-content/70 break-all mb-0">{signature}</p>
</div>
</div>
))}
</div>
);
};
23 changes: 23 additions & 0 deletions packages/nextjs/app/view/_components/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export enum SignatureStatus {
INVALID = "INVALID",
MATCH = "MATCH",
MISMATCH = "MISMATCH",
}

export const EIP1271_SPEC = {
magicValue: "0x1626ba7e",
abi: [
{
constant: true,
inputs: [
{ name: "_hash", type: "bytes32" },
{ name: "_sig", type: "bytes" },
],
name: "isValidSignature",
outputs: [{ name: "magicValue", type: "bytes4" }],
payable: false,
stateMutability: "view",
type: "function",
},
],
} as const;
65 changes: 65 additions & 0 deletions packages/nextjs/app/view/_components/useSignatureVerification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useEffect, useState } from "react";
import { SignatureStatus } from "./types";
import { checkEip1271 } from "./verify-signature";
import {
TypedDataDefinition,
hashMessage,
hashTypedData,
isAddress,
isHex,
recoverMessageAddress,
recoverTypedDataAddress,
} from "viem";
import { useClient } from "wagmi";

export const useSignatureVerification = (
message: string | null,
typedData: TypedDataDefinition | null,
signatures: string[],
addresses: string[],
) => {
const client = useClient();
const [addressChecks, setAddressChecks] = useState<SignatureStatus[]>([]);

useEffect(() => {
if (!client || (!message && !typedData) || !signatures.length || !addresses.length) return;

const verifySignatures = async () => {
const checks = await Promise.all(
signatures.map(async (sig, index) => {
if (index + 1 > addresses.length || !isAddress(addresses[index]) || !isHex(sig)) {
return SignatureStatus.INVALID;
}

try {
let signingAddress = null;
if (message) signingAddress = await recoverMessageAddress({ message, signature: sig });
if (typedData) signingAddress = await recoverTypedDataAddress({ ...typedData, signature: sig });

if (!signingAddress) return SignatureStatus.INVALID;
if (signingAddress.toLowerCase() === addresses[index].toLowerCase()) {
return SignatureStatus.MATCH;
}

let messageHash = null;
if (message) messageHash = hashMessage(message);
if (typedData) messageHash = hashTypedData(typedData);

if (!messageHash) return SignatureStatus.INVALID;

return checkEip1271(client, addresses[index], messageHash, sig);
} catch (error) {
console.error(`Signature verification failed for ${sig}:`, error);
return SignatureStatus.INVALID;
}
}),
);

setAddressChecks(checks);
};

verifySignatures();
}, [signatures, message, client, addresses, typedData]);

return addressChecks;
};
29 changes: 29 additions & 0 deletions packages/nextjs/app/view/_components/verify-signature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { EIP1271_SPEC, SignatureStatus } from "./types";
import { Address as AddressType, Client, Hex } from "viem";
import { getCode, readContract } from "viem/actions";

export const checkEip1271 = async (
client: Client,
address: AddressType,
message: string,
signature: Hex,
): Promise<SignatureStatus> => {
try {
const addressCode = await getCode(client, { address });
if (!addressCode || addressCode === "0x") {
return SignatureStatus.MISMATCH;
}

const returnValue = await readContract(client, {
address,
abi: EIP1271_SPEC.abi,
functionName: "isValidSignature",
args: [message, signature],
});

return returnValue === EIP1271_SPEC.magicValue ? SignatureStatus.MATCH : SignatureStatus.MISMATCH;
} catch (error) {
console.error("EIP1271 check failed:", error);
return SignatureStatus.MISMATCH;
}
};
Loading

0 comments on commit 019fa76

Please sign in to comment.