Skip to content
Merged
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
1 change: 1 addition & 0 deletions apps/mesh-cloud/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const config = {
"@typescript-eslint/prefer-optional-chain": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-redundant-type-constituents": "off",
"@typescript-eslint/prefer-nullish-coalescing": "off",
},
};
module.exports = config;
21 changes: 14 additions & 7 deletions apps/mesh-cloud/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,38 @@
"dev": "next dev",
"postinstall": "prisma generate",
"lint": "next lint",
"start": "next start"
"start": "next start",
"clean": "rm -rf .turbo && rm -rf .next && rm -rf node_modules && rm package-lock.json"
},
"dependencies": {
"@auth/prisma-adapter": "^1.6.0",
"@cardananium/cquisitor-lib": "^0.1.0-beta.12",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@heroicons/react": "^2.1.5",
"@meshsdk/core": "1.8.2",
"@meshsdk/react": "1.8.2",
"@meshsdk/core": "1.9.0-beta.34",
"@meshsdk/react": "1.9.0-beta.34",
"@mui/material": "^7.0.2",
"@prisma/client": "^5.14.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.7",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.7",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@t3-oss/env-nextjs": "^0.10.1",
"@tanstack/react-query": "^5.50.0",
"@textea/json-viewer": "^4.0.1",
"@trpc/client": "^11.0.0-rc.446",
"@trpc/next": "^11.0.0-rc.446",
"@trpc/react-query": "^11.0.0-rc.446",
"@trpc/server": "^11.0.0-rc.446",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"copy-to-clipboard": "^3.3.3",
"geist": "^1.3.0",
"lucide-react": "^0.436.0",
Expand Down Expand Up @@ -72,6 +80,5 @@
},
"ct3aMetadata": {
"initVersion": "7.37.0"
},
"packageManager": "npm@10.7.0"
}
}
}
130 changes: 130 additions & 0 deletions apps/mesh-cloud/src/apps/cquisitor/command.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"use client";

import { useCallback, useEffect, useState } from "react";

import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/components/ui/command";
import { Button } from "@/components/ui/button";
import * as cq from "@cardananium/cquisitor-lib";
import { nonCSLDecodeOption } from "./utils";

export function CquisitorCommand({
currentType,
setItem,
}: {
currentType: string;
setItem: (item: string) => void;
}) {
const [open, setOpen] = useState(false);
const [suggestions, setSuggestions] = useState<string[]>([]);
const [allTypes, setAllTypes] = useState<string[]>([]);

// Command + J to open panel
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "j" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((open) => !open);
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);

const updateTypeCount = useCallback((type: string) => {
const typeCounts = JSON.parse(localStorage.getItem("typeCounts") || "{}");
typeCounts[type] = (typeCounts[type] || 0) + 1;
localStorage.setItem("typeCounts", JSON.stringify(typeCounts));
}, []);

const getAllOptions = () => {
const allCslTypes = cq.get_decodable_types();
const allNonCslTypes = Object.keys(nonCSLDecodeOption);
return [...allCslTypes, ...allNonCslTypes];
};

const updateSuggestions = useCallback(() => {
const typeCounts: Record<string, number> = JSON.parse(
localStorage.getItem("typeCounts") || "{}",
);

// Convert the typeCounts object into an array of [type, count] pairs
const sortedTypes = Object.entries(typeCounts)
.sort(([, countA], [, countB]) => countB - countA) // Sort by count in descending order
.slice(0, 3) // Take the top 3 items
.map(([type]) => type); // Extract only the type names

setSuggestions(sortedTypes);

const allTypes = getAllOptions();
const filteredTypes = allTypes.filter(
(type) => !sortedTypes.includes(type),
);
setAllTypes(filteredTypes);
}, []);

useEffect(() => {
updateSuggestions();
}, [updateSuggestions]);

const onSelect = (item: string) => {
updateTypeCount(item);
updateSuggestions();
setItem(item);
setOpen(false);
};

return (
<>
<Button variant="ghost" onClick={() => setOpen(true)}>
<div className="flex gap-16 text-sm">
<span className="rounded-full text-white">{currentType}</span>
<div className="text-muted-foreground">
<span>Search </span>
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100">
<span className="text-xs">⌘</span>J
</kbd>
</div>
</div>
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
{suggestions.map((item) => (
<CommandItem
key={item}
onSelect={() => {
onSelect(item);
}}
>
<span>{item}</span>
</CommandItem>
))}
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
{allTypes.map((item) => (
<CommandItem
key={item}
onSelect={() => {
onSelect(item);
}}
>
<span>{item}</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</CommandDialog>
</>
);
}
146 changes: 146 additions & 0 deletions apps/mesh-cloud/src/apps/cquisitor/cquisitor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import CardSection from "@/components/card-section";
import CquisitorLayout from "./layout";
import { useEffect, useState } from "react";
import Metatags from "@/components/site/metatags";
import { Card } from "@/components/ui/card";

import { JsonViewer } from "@textea/json-viewer";
import { TopBar } from "./top-bar";
import { CquisitorCommand } from "./command";

import { Textarea } from "@/components/ui/textarea";
import {
decode,
getPositionDataType,
getTxAddressDataType,
getTxIdDataType,
nonCSLDecodeOption,
type DecodeType,
} from "./utils";
import * as cq from "@cardananium/cquisitor-lib";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";

export default function Cquisitor() {
const [apiKey, setApiKey] = useState("");
const [network, setNetwork] = useState("mainnet");
const [service, setService] = useState<"blockfrost" | "maestro">(
"blockfrost",
);
const [loading, setLoading] = useState(false);

const [cborHex, setCborHex] = useState("");
const [decodeType, setDecodeType] = useState<DecodeType>("decode-by-csl");
const [currentType, setCurrentType] = useState("Transaction");
const [currentData, setCurrentData] = useState<any>("");

// display
const [cborPosition, setCborPosition] = useState([0, 0]);
const [possibleTypes, setPossibleTypes] = useState<string[]>([]);

const setItem = (item: string) => {
setCurrentType(item);
if (nonCSLDecodeOption[item]) {
setDecodeType(nonCSLDecodeOption[item]);
} else {
setDecodeType("decode-by-csl");
}
};

useEffect(() => {
const decoded = decode(cborHex, decodeType, currentType);
setCurrentData(decoded);
const possibleTypes = cq.get_possible_types_for_input(cborHex);
setPossibleTypes(possibleTypes);
console.log("possibleTypes", possibleTypes);
}, [currentType, cborHex, decodeType]);

let showAsJson = true;
if (typeof currentData === "string" || currentData instanceof String) {
console.log(currentData);
showAsJson = false;
}

useEffect(() => {
const apiKey = localStorage.getItem("apiKey");
if (apiKey) setApiKey(apiKey);
const network = localStorage.getItem("network");
if (network) setNetwork(network);
}, []);

return (
<CquisitorLayout>
<Metatags title="Cquisitor" />
<TopBar
network={network}
setNetwork={setNetwork}
setService={setService}
apiKey={apiKey}
setApiKey={setApiKey}
loading={loading}
/>
<>
<CardSection
title="Investigate CBOR"
description="Paste your CBOR hex here to decode."
>
<>
<div className="grid gap-3">
<div className="grid w-full gap-2">
<Textarea
placeholder="Type your message here."
onChange={(e) => setCborHex(e.target.value)}
/>
</div>
</div>

{possibleTypes && possibleTypes.length > 0 && (
<>
<Label>Possible types to decode:</Label>
<div className="block gap-2">
{possibleTypes.map((type, index) => (
<Button
key={index}
variant="outline"
className="mb-2 mr-2 inline-block rounded px-4 py-2 text-white"
onClick={() => setItem(type)}
>
{type}
</Button>
))}
</div>
</>
)}
<CquisitorCommand currentType={currentType} setItem={setItem} />
</>
</CardSection>
</>
<>
<Card className="p-4">
<div className="w-full whitespace-pre-wrap break-all">
{showAsJson ? (
<JsonViewer
sx={{ fontSize: 14 }}
value={currentData}
theme="dark"
quotesOnKeys={false}
displayDataTypes={false}
rootName={false}
valueTypes={[
getPositionDataType(setCborPosition),
getTxIdDataType(network),
getTxAddressDataType(network),
]}
/>
) : (
currentData
)}
{/* {
"84a600d90102828258201e126e978ffbc3cd396fb2b69ce3368abb353443292e0ae56f6acf6f3c97022800825820e0b92c29cad3b1c8c8c192eae238f8f21748673b6ce296ec8c2fd7f1935bb3e902018182583900d161d64eef0eeb59f9124f520f8c8f3b717ed04198d54c8b17e604aea63c153fb3ea8a4ea4f165574ea91173756de0bf30222ca0e95a649a1a06b9f40a021a0002d5610b58202f299a81829e6e429ddd3930c838653dcc966735a6e4ad0e3b07bd17059c6d120dd9010281825820e0b92c29cad3b1c8c8c192eae238f8f21748673b6ce296ec8c2fd7f1935bb3e9050ed9010281581cd161d64eef0eeb59f9124f520f8c8f3b717ed04198d54c8b17e604aea206d901028159011659011301010032323232323225980099191919192cc004cdc3a400460106ea80062646464b30013370e900018059baa005899192cc004c04400a2b30013370e900018069baa003899192cc004cdc79bae30023010375401291010d48656c6c6f2c20576f726c6421008800c528201c332232330010010032259800800c52844c96600266e3cdd7180b0010024528c4cc00c00c005012180b000a0283758602260246024602460246024602460246024601e6ea8028dd7180098079baa3011300f37540084602200316403116403c6eb8c03c004c030dd5002c5900a18069807001180600098049baa0018b200e300a300b00230090013009002300700130043754003149a26cac80115cd2ab9d5573caae7d5d0aba2105a182000082d8799f4d48656c6c6f2c20576f726c6421ff820000f5f6"
} */}
</div>
</Card>
</>
</CquisitorLayout>
);
}
15 changes: 15 additions & 0 deletions apps/mesh-cloud/src/apps/cquisitor/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import LayoutPage from "@/components/layouts/page";
import Metatags from "@/components/site/metatags";

export default function CquisitorLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<LayoutPage pageTitle="Cquisitors" sideNav="cquisitor">
<Metatags title="Cquisitor" />
{children}
</LayoutPage>
);
}
Loading
Loading