-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1058e64
commit 019fa76
Showing
7 changed files
with
239 additions
and
202 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
47
packages/nextjs/app/view/_components/SignatureStatusIcon.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
65
packages/nextjs/app/view/_components/useSignatureVerification.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
}; |
Oops, something went wrong.