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
155 changes: 155 additions & 0 deletions packages/app/src/components/connect/AnchorRequestPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/**
* Approval screen for an incoming `anchor-request`: publishing a signed Canon
* declaration on-chain as a permanent `cnd1` commit+reveal pair, paid from
* this wallet. Shows the parsed declaration (what the signing key recognizes
* or revokes), the signer, and a permanence warning before `Connect.tsx`
* calls `anchorFromRequest`. The exact document is available to inspect —
* its bytes are what gets committed.
*/
import {
Alert,
AlertDescription,
AlertIcon,
AlertTitle,
Badge,
Box,
Button,
Code,
HStack,
Stack,
Text,
} from "@chakra-ui/react";
import { MdAnchor, MdWarning } from "react-icons/md";
import Card from "@app/components/Card";
import { canonDeclarationFromDocument } from "@app/connect/protocol";
import type { AnchorRequest } from "@app/connect/protocol";
import { sanitizeForDisplay } from "@lib/displayText";

export default function AnchorRequestPanel({
request,
busy,
onApprove,
onReject,
}: {
request: AnchorRequest;
busy: boolean;
onApprove: () => void;
onReject: () => void;
}) {
// Guaranteed parseable — normalizeAnchorEnvelope refused anything else.
const parsed = canonDeclarationFromDocument(request.document);
if (!parsed) return null;
const { declaration } = parsed;

return (
<Stack spacing={4}>
<Card p={5}>
<HStack justify="space-between" mb={4}>
<Text textStyle="label">Anchor a Canon declaration</Text>
<Badge colorScheme="green" display="flex" alignItems="center" gap={1}>
<MdAnchor /> Canon declaration
</Badge>
</HStack>

{request.origin || request.app ? (
<Box mb={4}>
<Text textStyle="label" mb={1}>
Requested by
</Text>
<Code w="100%" p={2} borderRadius="md" wordBreak="break-all">
{request.app ? `${sanitizeForDisplay(request.app)} — ` : ""}
{request.origin ? sanitizeForDisplay(request.origin) : "(no origin provided)"}
</Code>
</Box>
) : null}

<Box mb={4}>
<Text textStyle="label" mb={1}>
The declaration being published
</Text>
<Stack spacing={2} p={3} borderRadius="md" borderWidth="1px" fontSize="sm">
{declaration.declares.map((entry) => (
<Box key={entry.kind + entry.ref}>
<Text>
Key recognizes the{" "}
<b>
{entry.kind === "container"
? "collection"
: entry.kind === "work"
? "individual work"
: "creator token"}
</b>
{entry.label ? ` “${sanitizeForDisplay(entry.label)}”` : ""}
</Text>
<Code fontSize="xs" wordBreak="break-all">
{entry.ref}
</Code>
</Box>
))}
{declaration.revokes.map((ref) => (
<Box key={ref}>
<Text>
Key <b>withdraws recognition</b> of
</Text>
<Code fontSize="xs" wordBreak="break-all">
{ref}
</Code>
</Box>
))}
<Text color="gray.400" fontSize="xs">
Signer {sanitizeForDisplay(declaration.signer)} · dated{" "}
{sanitizeForDisplay(declaration.issued)} ·{" "}
{declaration.expires
? `expires ${sanitizeForDisplay(declaration.expires)}`
: "no expiry"}
</Text>
{declaration.comment ? (
<Text color="gray.400" fontSize="xs">
“{sanitizeForDisplay(declaration.comment)}”
</Text>
) : null}
</Stack>
</Box>

<Alert status="warning" borderRadius="lg" mb={4}>
<AlertIcon as={MdWarning} />
<Box>
<AlertTitle fontSize="sm">Anchoring is permanent</AlertTitle>
<AlertDescription fontSize="sm">
This publishes the declaration to the blockchain forever — it cannot be deleted,
only superseded by a later one. This wallet pays the transaction fees.
</AlertDescription>
</Box>
</Alert>

<Box mb={4}>
<Text textStyle="label" mb={1}>
Exact document to be committed ({request.document.length} bytes)
</Text>
<Code
display="block"
w="100%"
p={2}
borderRadius="md"
whiteSpace="pre-wrap"
wordBreak="break-all"
fontSize="xs"
maxH="200px"
overflowY="auto"
>
{sanitizeForDisplay(request.document)}
</Code>
</Box>

<HStack>
<Button colorScheme="green" onClick={onApprove} isLoading={busy}>
{request.broadcast === false ? "Build (no broadcast)" : "Anchor on-chain"}
</Button>
<Button variant="ghost" onClick={onReject} isDisabled={busy}>
Reject
</Button>
</HStack>
</Card>
</Stack>
);
}
107 changes: 107 additions & 0 deletions packages/app/src/components/connect/AnchorResultPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* Result screen after an `anchor-request` completes: the commit + reveal
* txids (or, for a dry run — `broadcast: false` — the raw unsent hex) and
* the document's docHash identity. Mirrors `MintResultPanel`'s shape.
*/
import {
Alert,
AlertDescription,
AlertIcon,
AlertTitle,
Box,
Button,
Code,
Divider,
Stack,
Text,
useClipboard,
} from "@chakra-ui/react";
import { MdCheck, MdContentCopy } from "react-icons/md";
import Card from "@app/components/Card";
import type { AnchorResult } from "@app/connect/protocol";

function CopyField({ label, value }: { label: string; value: string }) {
const { onCopy, hasCopied } = useClipboard(value);
return (
<Box mt={3}>
<Text textStyle="label" mb={1}>
{label}
</Text>
<Code
display="block"
w="100%"
p={2}
borderRadius="md"
whiteSpace="pre-wrap"
wordBreak="break-all"
fontSize="xs"
>
{value}
</Code>
<Button
mt={1}
size="sm"
leftIcon={hasCopied ? <MdCheck /> : <MdContentCopy />}
onClick={onCopy}
variant="ghost"
>
{hasCopied ? "Copied!" : "Copy"}
</Button>
</Box>
);
}

export default function AnchorResultPanel({
result,
onDone,
}: {
result: AnchorResult;
onDone: () => void;
}) {
return (
<Stack spacing={4}>
{result.broadcast ? (
<Alert status="success" borderRadius="lg">
<AlertIcon />
<Box>
<AlertTitle>Anchored</AlertTitle>
<AlertDescription fontSize="sm">
The declaration is now a permanent on-chain record.
</AlertDescription>
</Box>
</Alert>
) : (
<Alert status="info" borderRadius="lg">
<AlertIcon />
<Box>
<AlertTitle>Built &amp; signed — not broadcast</AlertTitle>
<AlertDescription fontSize="sm">
Nothing was sent. Decode the hex below to verify before using
this in production.
</AlertDescription>
</Box>
</Alert>
)}

<Card p={5}>
{result.broadcast ? (
<>
<CopyField label="Reveal transaction (the anchor)" value={result.revealTxid!} />
<CopyField label="Commit transaction" value={result.commitTxid!} />
</>
) : (
<>
<CopyField label="Reveal transaction hex" value={result.revealHex!} />
<CopyField label="Commit transaction hex" value={result.commitHex!} />
</>
)}
<Divider my={3} />
<CopyField label="Document hash (docHash)" value={result.docHash} />
</Card>

<Button variant="solid" onClick={onDone}>
Done
</Button>
</Stack>
);
}
Loading