Aditya Arora 10 months ago
parent
commit
0f0b91f641

+ 5 - 0
apps/entropy-debug/next-env.d.ts

@@ -0,0 +1,5 @@
+/// <reference types="next" />
+/// <reference types="next/image-types/global" />
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

+ 51 - 0
apps/entropy-debug/package.json

@@ -0,0 +1,51 @@
+{
+  "name": "entropy-debug-app",
+  "version": "0.1.0",
+  "description": "Debugging tool for entropy",
+  "private": true,
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/pyth-network/pyth-crosschain",
+    "directory": "apps/entropy-debug"
+  },
+  "publishConfig": {
+    "access": "public"
+  },
+  "scripts": {
+    "dev": "next dev",
+    "build": "next build",
+    "start": "next start",
+    "lint": "next lint"
+  },
+  "license": "Apache-2.0",
+  "dependencies": {
+    "@radix-ui/react-select": "^2.1.2",
+    "@radix-ui/react-slot": "^1.1.0",
+    "@radix-ui/react-switch": "^1.1.1",
+    "class-variance-authority": "^0.7.1",
+    "clsx": "^2.1.1",
+    "ethers": "^6.13.4",
+    "highlight.js": "^11.10.0",
+    "lucide-react": "^0.465.0",
+    "next": "15.0.3",
+    "react": "19.0.0-rc-66855b96-20241106",
+    "react-dom": "19.0.0-rc-66855b96-20241106",
+    "tailwind-merge": "^2.5.5",
+    "tailwindcss-animate": "^1.0.7",
+    "viem": "^2.21.53"
+  },
+  "devDependencies": {
+    "@cprussin/eslint-config": "catalog:",
+    "@cprussin/jest-config": "catalog:",
+    "@cprussin/prettier-config": "catalog:",
+    "@cprussin/tsconfig": "catalog:",
+    "@types/node": "^20",
+    "@types/react": "^18",
+    "@types/react-dom": "^18",
+    "eslint": "^8",
+    "eslint-config-next": "15.0.3",
+    "postcss": "^8",
+    "tailwindcss": "^3.4.1",
+    "typescript": "^5"
+  }
+}

BIN
apps/entropy-debug/src/app/favicon.ico


BIN
apps/entropy-debug/src/app/fonts/GeistMonoVF.woff


BIN
apps/entropy-debug/src/app/fonts/GeistVF.woff


+ 72 - 0
apps/entropy-debug/src/app/globals.css

@@ -0,0 +1,72 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+body {
+  font-family: Arial, Helvetica, sans-serif;
+}
+
+@layer base {
+  :root {
+    --background: 0 0% 100%;
+    --foreground: 0 0% 3.9%;
+    --card: 0 0% 100%;
+    --card-foreground: 0 0% 3.9%;
+    --popover: 0 0% 100%;
+    --popover-foreground: 0 0% 3.9%;
+    --primary: 0 0% 9%;
+    --primary-foreground: 0 0% 98%;
+    --secondary: 0 0% 96.1%;
+    --secondary-foreground: 0 0% 9%;
+    --muted: 0 0% 96.1%;
+    --muted-foreground: 0 0% 45.1%;
+    --accent: 0 0% 96.1%;
+    --accent-foreground: 0 0% 9%;
+    --destructive: 0 84.2% 60.2%;
+    --destructive-foreground: 0 0% 98%;
+    --border: 0 0% 89.8%;
+    --input: 0 0% 89.8%;
+    --ring: 0 0% 3.9%;
+    --chart-1: 12 76% 61%;
+    --chart-2: 173 58% 39%;
+    --chart-3: 197 37% 24%;
+    --chart-4: 43 74% 66%;
+    --chart-5: 27 87% 67%;
+    --radius: 0.5rem;
+  }
+  .dark {
+    --background: 0 0% 3.9%;
+    --foreground: 0 0% 98%;
+    --card: 0 0% 3.9%;
+    --card-foreground: 0 0% 98%;
+    --popover: 0 0% 3.9%;
+    --popover-foreground: 0 0% 98%;
+    --primary: 0 0% 98%;
+    --primary-foreground: 0 0% 9%;
+    --secondary: 0 0% 14.9%;
+    --secondary-foreground: 0 0% 98%;
+    --muted: 0 0% 14.9%;
+    --muted-foreground: 0 0% 63.9%;
+    --accent: 0 0% 14.9%;
+    --accent-foreground: 0 0% 98%;
+    --destructive: 0 62.8% 30.6%;
+    --destructive-foreground: 0 0% 98%;
+    --border: 0 0% 14.9%;
+    --input: 0 0% 14.9%;
+    --ring: 0 0% 83.1%;
+    --chart-1: 220 70% 50%;
+    --chart-2: 160 60% 45%;
+    --chart-3: 30 80% 55%;
+    --chart-4: 280 65% 60%;
+    --chart-5: 340 75% 55%;
+  }
+}
+
+@layer base {
+  * {
+    @apply border-border;
+  }
+  body {
+    @apply bg-background text-foreground;
+  }
+}

+ 35 - 0
apps/entropy-debug/src/app/layout.tsx

@@ -0,0 +1,35 @@
+import type { Metadata } from "next";
+import localFont from "next/font/local";
+import "./globals.css";
+
+const geistSans = localFont({
+  src: "./fonts/GeistVF.woff",
+  variable: "--font-geist-sans",
+  weight: "100 900",
+});
+const geistMono = localFont({
+  src: "./fonts/GeistMonoVF.woff",
+  variable: "--font-geist-mono",
+  weight: "100 900",
+});
+
+export const metadata: Metadata = {
+  title: "Pyth Entropy Debug App",
+  description: "Pyth Entropy Debug App",
+};
+
+export default function RootLayout({
+  children,
+}: Readonly<{
+  children: React.ReactNode;
+}>) {
+  return (
+    <html lang="en">
+      <body
+        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
+      >
+        {children}
+      </body>
+    </html>
+  );
+}

+ 175 - 0
apps/entropy-debug/src/app/page.tsx

@@ -0,0 +1,175 @@
+"use client"
+
+import * as React from "react"
+import { Switch } from "../components/ui/switch"
+import { Input } from "../components/ui/input"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select"
+import { useState, useMemo, useCallback, useEffect, useRef } from "react"
+import { EntropyDeployments } from "../store/EntropyDeployments"
+import { isValidTxHash } from "../lib/utils"
+import { requestCallback } from "../lib/revelation"
+import hljs from 'highlight.js/lib/core';
+import bash from 'highlight.js/lib/languages/bash';
+import 'highlight.js/styles/github-dark.css'; // You can choose different themes
+
+// Register the bash language
+hljs.registerLanguage('bash', bash);
+
+class BaseError extends Error {
+  constructor(message: string) {
+    super(message)
+    this.name = "BaseError"
+  }
+}
+
+class InvalidTxHashError extends BaseError {
+  constructor(message: string) {
+    super(message)
+    this.name = "InvalidTxHashError"
+  }
+}
+
+
+enum TxStateType {
+  NotLoaded,
+  Loading,
+  Success,
+  Error
+}
+
+const TxState = {
+  NotLoaded: () => ({status: TxStateType.NotLoaded as const}),
+  Loading: () => ({status: TxStateType.Loading as const}),
+  Success: (data: string) => ({status: TxStateType.Success as const, data}),
+  Error: (error: unknown) => ({status: TxStateType.Error as const, error}),
+}
+
+type TxStateContext = ReturnType<typeof TxState.NotLoaded> | ReturnType<typeof TxState.Loading> | ReturnType<typeof TxState.Success> | ReturnType<typeof TxState.Error>
+
+export default function PythEntropyDebugApp() {
+  const [state, setState] = useState<TxStateContext>(TxState.NotLoaded());
+  const [isMainnet, setIsMainnet] = useState<boolean>(false);
+  const [txHash, setTxHash] = useState<string>("");
+  const [error, setError] = useState<BaseError | null>(null);
+  const [selectedChain, setSelectedChain] = useState<string>("");
+
+  const validateTxHash = (hash: string) => {
+    if (!isValidTxHash(hash) && hash !== "") {
+      setError(new InvalidTxHashError("Transaction hash must be 64 hexadecimal characters"));
+    } else {
+      setError(null);
+    }
+    setTxHash(hash);
+  };
+
+  const availableChains = useMemo(() => {
+    return Object.entries(EntropyDeployments)
+      .filter(([, deployment]) => deployment.network === (isMainnet ? "mainnet" : "testnet"))
+      .map(([key]) => key);
+  }, [isMainnet]);
+
+  const oncClickFetchInfo = useCallback(() => {
+    setState(TxState.Loading());
+    requestCallback(txHash, selectedChain)
+      .then((data) => {
+        setState(TxState.Success(data));
+      })
+      .catch((error) => {
+        setState(TxState.Error(error));
+      });
+  }, [txHash, selectedChain]);
+
+  const Info = ({state}: {state: TxStateContext}) => {
+    const preRef = useRef<HTMLPreElement>(null);
+
+    useEffect(() => {
+      if (preRef.current && state.status === TxStateType.Success) {
+        hljs.highlightElement(preRef.current);
+      }
+    }, [state]);
+
+    switch (state.status) {
+      case TxStateType.NotLoaded:
+        return <div>Not loaded</div>
+      case TxStateType.Loading:
+        return <div>Loading...</div>
+      case TxStateType.Success:
+        return (
+          <div className="mt-4 p-4 bg-gray-100 rounded w-full max-w-3xl">
+            <p className="mb-2">Please run the following command in your terminal:</p>
+            <div className="relative">
+              <pre
+                ref={preRef}
+                className="bg-black text-white p-4 rounded overflow-x-auto whitespace-pre-wrap break-words"
+              >
+                <code className="language-bash">{state.data}</code>
+              </pre>
+              <button
+                onClick={() => navigator.clipboard.writeText(state.data)}
+                className="absolute top-2 right-2 bg-gray-700 text-white px-3 py-1 rounded hover:bg-gray-600"
+              >
+                Copy
+              </button>
+            </div>
+          </div>
+        )
+      case TxStateType.Error:
+        return (
+          <div className="mt-4 p-4 bg-red-100 border border-red-400 rounded">
+            <div className="text-red-600">{String(state.error)}</div>
+          </div>
+        )
+    }
+  }
+
+  return (
+    <div className="flex flex-col items-center justify-start h-screen">
+      <h1 className="text-4xl font-bold mt-8">Pyth Entropy Debug App</h1>
+
+      <div className="flex items-center space-x-2 mt-4">
+        <label htmlFor="network-mode">Testnet</label>
+        <Switch
+          id="network-mode"
+          defaultChecked={false}
+          onCheckedChange={setIsMainnet}
+        />
+        <label htmlFor="network-mode">Mainnet</label>
+      </div>
+      <div className="mt-4">
+          <Select onValueChange={setSelectedChain} value={selectedChain}>
+          <SelectTrigger>
+            <SelectValue placeholder="Select Chain" />
+          </SelectTrigger>
+          <SelectContent>
+            {availableChains.map((chain) => (
+              <SelectItem key={chain} value={chain}>
+                {chain.charAt(0).toUpperCase() + chain.slice(1).replace(/-/g, ' ')}
+              </SelectItem>
+            ))}
+          </SelectContent>
+        </Select>
+      </div>
+      <div className="mt-4">
+        <label htmlFor="tx-hash" className="mr-2">Request Transaction Hash:</label>
+        <Input
+          minLength={64}
+          id="tx-hash"
+          className={`border rounded p-2 w-full ${error ? 'border-red-500' : ''}`}
+          placeholder="Enter Request Transaction Hash:"
+          value={txHash}
+          onChange={(e) => validateTxHash(e.target.value)}
+        />
+        {error && <p className="text-red-500 text-sm mt-1">{error.message}</p>}
+      </div>
+      <div className="mt-4">
+        <button
+          className="bg-blue-500 text-white p-2 rounded"
+          onClick={oncClickFetchInfo}
+        >
+          Fetch Info
+        </button>
+      </div>
+      <Info state={state} />
+    </div>
+  );
+}

+ 56 - 0
apps/entropy-debug/src/components/ui/button.tsx

@@ -0,0 +1,56 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "../../lib/utils"
+
+const buttonVariants = cva(
+  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+  {
+    variants: {
+      variant: {
+        default: "bg-primary text-primary-foreground hover:bg-primary/90",
+        destructive:
+          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+        outline:
+          "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+        secondary:
+          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+        ghost: "hover:bg-accent hover:text-accent-foreground",
+        link: "text-primary underline-offset-4 hover:underline",
+      },
+      size: {
+        default: "h-10 px-4 py-2",
+        sm: "h-9 rounded-md px-3",
+        lg: "h-11 rounded-md px-8",
+        icon: "h-10 w-10",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+      size: "default",
+    },
+  }
+)
+
+export interface ButtonProps
+  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
+    VariantProps<typeof buttonVariants> {
+  asChild?: boolean
+}
+
+const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
+  ({ className, variant, size, asChild = false, ...props }, ref) => {
+    const Comp = asChild ? Slot : "button"
+    return (
+      <Comp
+        className={cn(buttonVariants({ variant, size, className }))}
+        ref={ref}
+        {...props}
+      />
+    )
+  }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }

+ 22 - 0
apps/entropy-debug/src/components/ui/input.tsx

@@ -0,0 +1,22 @@
+import * as React from "react";
+
+import { cn } from "../../lib/utils";
+
+const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
+  ({ className, type, ...props }, ref) => {
+    return (
+      <input
+        type={type}
+        className={cn(
+          "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+          className
+        )}
+        ref={ref}
+        {...props}
+      />
+    );
+  }
+);
+Input.displayName = "Input";
+
+export { Input };

+ 160 - 0
apps/entropy-debug/src/components/ui/select.tsx

@@ -0,0 +1,160 @@
+"use client";
+
+import * as React from "react";
+import * as SelectPrimitive from "@radix-ui/react-select";
+import { Check, ChevronDown, ChevronUp } from "lucide-react";
+
+import { cn } from "../../lib/utils";
+
+const Select = SelectPrimitive.Root;
+
+const SelectGroup = SelectPrimitive.Group;
+
+const SelectValue = SelectPrimitive.Value;
+
+const SelectTrigger = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
+>(({ className, children, ...props }, ref) => (
+  <SelectPrimitive.Trigger
+    ref={ref}
+    className={cn(
+      "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
+      className
+    )}
+    {...props}
+  >
+    {children}
+    <SelectPrimitive.Icon asChild>
+      <ChevronDown className="h-4 w-4 opacity-50" />
+    </SelectPrimitive.Icon>
+  </SelectPrimitive.Trigger>
+));
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
+
+const SelectScrollUpButton = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
+>(({ className, ...props }, ref) => (
+  <SelectPrimitive.ScrollUpButton
+    ref={ref}
+    className={cn(
+      "flex cursor-default items-center justify-center py-1",
+      className
+    )}
+    {...props}
+  >
+    <ChevronUp className="h-4 w-4" />
+  </SelectPrimitive.ScrollUpButton>
+));
+SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
+
+const SelectScrollDownButton = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
+>(({ className, ...props }, ref) => (
+  <SelectPrimitive.ScrollDownButton
+    ref={ref}
+    className={cn(
+      "flex cursor-default items-center justify-center py-1",
+      className
+    )}
+    {...props}
+  >
+    <ChevronDown className="h-4 w-4" />
+  </SelectPrimitive.ScrollDownButton>
+));
+SelectScrollDownButton.displayName =
+  SelectPrimitive.ScrollDownButton.displayName;
+
+const SelectContent = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
+>(({ className, children, position = "popper", ...props }, ref) => (
+  <SelectPrimitive.Portal>
+    <SelectPrimitive.Content
+      ref={ref}
+      className={cn(
+        "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+        position === "popper" &&
+          "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
+        className
+      )}
+      position={position}
+      {...props}
+    >
+      <SelectScrollUpButton />
+      <SelectPrimitive.Viewport
+        className={cn(
+          "p-1",
+          position === "popper" &&
+            "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
+        )}
+      >
+        {children}
+      </SelectPrimitive.Viewport>
+      <SelectScrollDownButton />
+    </SelectPrimitive.Content>
+  </SelectPrimitive.Portal>
+));
+SelectContent.displayName = SelectPrimitive.Content.displayName;
+
+const SelectLabel = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Label>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
+>(({ className, ...props }, ref) => (
+  <SelectPrimitive.Label
+    ref={ref}
+    className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
+    {...props}
+  />
+));
+SelectLabel.displayName = SelectPrimitive.Label.displayName;
+
+const SelectItem = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
+>(({ className, children, ...props }, ref) => (
+  <SelectPrimitive.Item
+    ref={ref}
+    className={cn(
+      "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <SelectPrimitive.ItemIndicator>
+        <Check className="h-4 w-4" />
+      </SelectPrimitive.ItemIndicator>
+    </span>
+
+    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
+  </SelectPrimitive.Item>
+));
+SelectItem.displayName = SelectPrimitive.Item.displayName;
+
+const SelectSeparator = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Separator>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+  <SelectPrimitive.Separator
+    ref={ref}
+    className={cn("-mx-1 my-1 h-px bg-muted", className)}
+    {...props}
+  />
+));
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
+
+export {
+  Select,
+  SelectGroup,
+  SelectValue,
+  SelectTrigger,
+  SelectContent,
+  SelectLabel,
+  SelectItem,
+  SelectSeparator,
+  SelectScrollUpButton,
+  SelectScrollDownButton,
+};

+ 29 - 0
apps/entropy-debug/src/components/ui/switch.tsx

@@ -0,0 +1,29 @@
+"use client";
+
+import * as React from "react";
+import * as SwitchPrimitives from "@radix-ui/react-switch";
+
+import { cn } from "../../lib/utils";
+
+const Switch = React.forwardRef<
+  React.ElementRef<typeof SwitchPrimitives.Root>,
+  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
+>(({ className, ...props }, ref) => (
+  <SwitchPrimitives.Root
+    className={cn(
+      "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
+      className
+    )}
+    {...props}
+    ref={ref}
+  >
+    <SwitchPrimitives.Thumb
+      className={cn(
+        "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
+      )}
+    />
+  </SwitchPrimitives.Root>
+));
+Switch.displayName = SwitchPrimitives.Root.displayName;
+
+export { Switch };

+ 1000 - 0
apps/entropy-debug/src/lib/EntropyAbi.ts

@@ -0,0 +1,1000 @@
+export const EntropyAbi = [
+  {
+    anonymous: false,
+    inputs: [
+      {
+        indexed: false,
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        indexed: false,
+        internalType: "address",
+        name: "oldFeeManager",
+        type: "address",
+      },
+      {
+        indexed: false,
+        internalType: "address",
+        name: "newFeeManager",
+        type: "address",
+      },
+    ],
+    name: "ProviderFeeManagerUpdated",
+    type: "event",
+  },
+  {
+    anonymous: false,
+    inputs: [
+      {
+        indexed: false,
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        indexed: false,
+        internalType: "uint128",
+        name: "oldFee",
+        type: "uint128",
+      },
+      {
+        indexed: false,
+        internalType: "uint128",
+        name: "newFee",
+        type: "uint128",
+      },
+    ],
+    name: "ProviderFeeUpdated",
+    type: "event",
+  },
+  {
+    anonymous: false,
+    inputs: [
+      {
+        indexed: false,
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        indexed: false,
+        internalType: "uint32",
+        name: "oldMaxNumHashes",
+        type: "uint32",
+      },
+      {
+        indexed: false,
+        internalType: "uint32",
+        name: "newMaxNumHashes",
+        type: "uint32",
+      },
+    ],
+    name: "ProviderMaxNumHashesAdvanced",
+    type: "event",
+  },
+  {
+    anonymous: false,
+    inputs: [
+      {
+        indexed: false,
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        indexed: false,
+        internalType: "bytes",
+        name: "oldUri",
+        type: "bytes",
+      },
+      {
+        indexed: false,
+        internalType: "bytes",
+        name: "newUri",
+        type: "bytes",
+      },
+    ],
+    name: "ProviderUriUpdated",
+    type: "event",
+  },
+  {
+    anonymous: false,
+    inputs: [
+      {
+        components: [
+          {
+            internalType: "uint128",
+            name: "feeInWei",
+            type: "uint128",
+          },
+          {
+            internalType: "uint128",
+            name: "accruedFeesInWei",
+            type: "uint128",
+          },
+          {
+            internalType: "bytes32",
+            name: "originalCommitment",
+            type: "bytes32",
+          },
+          {
+            internalType: "uint64",
+            name: "originalCommitmentSequenceNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "bytes",
+            name: "commitmentMetadata",
+            type: "bytes",
+          },
+          {
+            internalType: "bytes",
+            name: "uri",
+            type: "bytes",
+          },
+          {
+            internalType: "uint64",
+            name: "endSequenceNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "uint64",
+            name: "sequenceNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "bytes32",
+            name: "currentCommitment",
+            type: "bytes32",
+          },
+          {
+            internalType: "uint64",
+            name: "currentCommitmentSequenceNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "address",
+            name: "feeManager",
+            type: "address",
+          },
+          {
+            internalType: "uint32",
+            name: "maxNumHashes",
+            type: "uint32",
+          },
+        ],
+        indexed: false,
+        internalType: "struct EntropyStructs.ProviderInfo",
+        name: "provider",
+        type: "tuple",
+      },
+    ],
+    name: "Registered",
+    type: "event",
+  },
+  {
+    anonymous: false,
+    inputs: [
+      {
+        components: [
+          {
+            internalType: "address",
+            name: "provider",
+            type: "address",
+          },
+          {
+            internalType: "uint64",
+            name: "sequenceNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "uint32",
+            name: "numHashes",
+            type: "uint32",
+          },
+          {
+            internalType: "bytes32",
+            name: "commitment",
+            type: "bytes32",
+          },
+          {
+            internalType: "uint64",
+            name: "blockNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "address",
+            name: "requester",
+            type: "address",
+          },
+          {
+            internalType: "bool",
+            name: "useBlockhash",
+            type: "bool",
+          },
+          {
+            internalType: "bool",
+            name: "isRequestWithCallback",
+            type: "bool",
+          },
+        ],
+        indexed: false,
+        internalType: "struct EntropyStructs.Request",
+        name: "request",
+        type: "tuple",
+      },
+    ],
+    name: "Requested",
+    type: "event",
+  },
+  {
+    anonymous: false,
+    inputs: [
+      {
+        indexed: true,
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        indexed: true,
+        internalType: "address",
+        name: "requestor",
+        type: "address",
+      },
+      {
+        indexed: true,
+        internalType: "uint64",
+        name: "sequenceNumber",
+        type: "uint64",
+      },
+      {
+        indexed: false,
+        internalType: "bytes32",
+        name: "userRandomNumber",
+        type: "bytes32",
+      },
+      {
+        components: [
+          {
+            internalType: "address",
+            name: "provider",
+            type: "address",
+          },
+          {
+            internalType: "uint64",
+            name: "sequenceNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "uint32",
+            name: "numHashes",
+            type: "uint32",
+          },
+          {
+            internalType: "bytes32",
+            name: "commitment",
+            type: "bytes32",
+          },
+          {
+            internalType: "uint64",
+            name: "blockNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "address",
+            name: "requester",
+            type: "address",
+          },
+          {
+            internalType: "bool",
+            name: "useBlockhash",
+            type: "bool",
+          },
+          {
+            internalType: "bool",
+            name: "isRequestWithCallback",
+            type: "bool",
+          },
+        ],
+        indexed: false,
+        internalType: "struct EntropyStructs.Request",
+        name: "request",
+        type: "tuple",
+      },
+    ],
+    name: "RequestedWithCallback",
+    type: "event",
+  },
+  {
+    anonymous: false,
+    inputs: [
+      {
+        components: [
+          {
+            internalType: "address",
+            name: "provider",
+            type: "address",
+          },
+          {
+            internalType: "uint64",
+            name: "sequenceNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "uint32",
+            name: "numHashes",
+            type: "uint32",
+          },
+          {
+            internalType: "bytes32",
+            name: "commitment",
+            type: "bytes32",
+          },
+          {
+            internalType: "uint64",
+            name: "blockNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "address",
+            name: "requester",
+            type: "address",
+          },
+          {
+            internalType: "bool",
+            name: "useBlockhash",
+            type: "bool",
+          },
+          {
+            internalType: "bool",
+            name: "isRequestWithCallback",
+            type: "bool",
+          },
+        ],
+        indexed: false,
+        internalType: "struct EntropyStructs.Request",
+        name: "request",
+        type: "tuple",
+      },
+      {
+        indexed: false,
+        internalType: "bytes32",
+        name: "userRevelation",
+        type: "bytes32",
+      },
+      {
+        indexed: false,
+        internalType: "bytes32",
+        name: "providerRevelation",
+        type: "bytes32",
+      },
+      {
+        indexed: false,
+        internalType: "bytes32",
+        name: "blockHash",
+        type: "bytes32",
+      },
+      {
+        indexed: false,
+        internalType: "bytes32",
+        name: "randomNumber",
+        type: "bytes32",
+      },
+    ],
+    name: "Revealed",
+    type: "event",
+  },
+  {
+    anonymous: false,
+    inputs: [
+      {
+        components: [
+          {
+            internalType: "address",
+            name: "provider",
+            type: "address",
+          },
+          {
+            internalType: "uint64",
+            name: "sequenceNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "uint32",
+            name: "numHashes",
+            type: "uint32",
+          },
+          {
+            internalType: "bytes32",
+            name: "commitment",
+            type: "bytes32",
+          },
+          {
+            internalType: "uint64",
+            name: "blockNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "address",
+            name: "requester",
+            type: "address",
+          },
+          {
+            internalType: "bool",
+            name: "useBlockhash",
+            type: "bool",
+          },
+          {
+            internalType: "bool",
+            name: "isRequestWithCallback",
+            type: "bool",
+          },
+        ],
+        indexed: false,
+        internalType: "struct EntropyStructs.Request",
+        name: "request",
+        type: "tuple",
+      },
+      {
+        indexed: false,
+        internalType: "bytes32",
+        name: "userRandomNumber",
+        type: "bytes32",
+      },
+      {
+        indexed: false,
+        internalType: "bytes32",
+        name: "providerRevelation",
+        type: "bytes32",
+      },
+      {
+        indexed: false,
+        internalType: "bytes32",
+        name: "randomNumber",
+        type: "bytes32",
+      },
+    ],
+    name: "RevealedWithCallback",
+    type: "event",
+  },
+  {
+    anonymous: false,
+    inputs: [
+      {
+        indexed: false,
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        indexed: false,
+        internalType: "address",
+        name: "recipient",
+        type: "address",
+      },
+      {
+        indexed: false,
+        internalType: "uint128",
+        name: "withdrawnAmount",
+        type: "uint128",
+      },
+    ],
+    name: "Withdrawal",
+    type: "event",
+  },
+  {
+    inputs: [
+      {
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        internalType: "uint64",
+        name: "advancedSequenceNumber",
+        type: "uint64",
+      },
+      {
+        internalType: "bytes32",
+        name: "providerRevelation",
+        type: "bytes32",
+      },
+    ],
+    name: "advanceProviderCommitment",
+    outputs: [],
+    stateMutability: "nonpayable",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "bytes32",
+        name: "userRandomness",
+        type: "bytes32",
+      },
+      {
+        internalType: "bytes32",
+        name: "providerRandomness",
+        type: "bytes32",
+      },
+      {
+        internalType: "bytes32",
+        name: "blockHash",
+        type: "bytes32",
+      },
+    ],
+    name: "combineRandomValues",
+    outputs: [
+      {
+        internalType: "bytes32",
+        name: "combinedRandomness",
+        type: "bytes32",
+      },
+    ],
+    stateMutability: "pure",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "bytes32",
+        name: "userRandomness",
+        type: "bytes32",
+      },
+    ],
+    name: "constructUserCommitment",
+    outputs: [
+      {
+        internalType: "bytes32",
+        name: "userCommitment",
+        type: "bytes32",
+      },
+    ],
+    stateMutability: "pure",
+    type: "function",
+  },
+  {
+    inputs: [],
+    name: "getAccruedPythFees",
+    outputs: [
+      {
+        internalType: "uint128",
+        name: "accruedPythFeesInWei",
+        type: "uint128",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+  {
+    inputs: [],
+    name: "getDefaultProvider",
+    outputs: [
+      {
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+    ],
+    name: "getFee",
+    outputs: [
+      {
+        internalType: "uint128",
+        name: "feeAmount",
+        type: "uint128",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+    ],
+    name: "getProviderInfo",
+    outputs: [
+      {
+        components: [
+          {
+            internalType: "uint128",
+            name: "feeInWei",
+            type: "uint128",
+          },
+          {
+            internalType: "uint128",
+            name: "accruedFeesInWei",
+            type: "uint128",
+          },
+          {
+            internalType: "bytes32",
+            name: "originalCommitment",
+            type: "bytes32",
+          },
+          {
+            internalType: "uint64",
+            name: "originalCommitmentSequenceNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "bytes",
+            name: "commitmentMetadata",
+            type: "bytes",
+          },
+          {
+            internalType: "bytes",
+            name: "uri",
+            type: "bytes",
+          },
+          {
+            internalType: "uint64",
+            name: "endSequenceNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "uint64",
+            name: "sequenceNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "bytes32",
+            name: "currentCommitment",
+            type: "bytes32",
+          },
+          {
+            internalType: "uint64",
+            name: "currentCommitmentSequenceNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "address",
+            name: "feeManager",
+            type: "address",
+          },
+          {
+            internalType: "uint32",
+            name: "maxNumHashes",
+            type: "uint32",
+          },
+        ],
+        internalType: "struct EntropyStructs.ProviderInfo",
+        name: "info",
+        type: "tuple",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        internalType: "uint64",
+        name: "sequenceNumber",
+        type: "uint64",
+      },
+    ],
+    name: "getRequest",
+    outputs: [
+      {
+        components: [
+          {
+            internalType: "address",
+            name: "provider",
+            type: "address",
+          },
+          {
+            internalType: "uint64",
+            name: "sequenceNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "uint32",
+            name: "numHashes",
+            type: "uint32",
+          },
+          {
+            internalType: "bytes32",
+            name: "commitment",
+            type: "bytes32",
+          },
+          {
+            internalType: "uint64",
+            name: "blockNumber",
+            type: "uint64",
+          },
+          {
+            internalType: "address",
+            name: "requester",
+            type: "address",
+          },
+          {
+            internalType: "bool",
+            name: "useBlockhash",
+            type: "bool",
+          },
+          {
+            internalType: "bool",
+            name: "isRequestWithCallback",
+            type: "bool",
+          },
+        ],
+        internalType: "struct EntropyStructs.Request",
+        name: "req",
+        type: "tuple",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "uint128",
+        name: "feeInWei",
+        type: "uint128",
+      },
+      {
+        internalType: "bytes32",
+        name: "commitment",
+        type: "bytes32",
+      },
+      {
+        internalType: "bytes",
+        name: "commitmentMetadata",
+        type: "bytes",
+      },
+      {
+        internalType: "uint64",
+        name: "chainLength",
+        type: "uint64",
+      },
+      {
+        internalType: "bytes",
+        name: "uri",
+        type: "bytes",
+      },
+    ],
+    name: "register",
+    outputs: [],
+    stateMutability: "nonpayable",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        internalType: "bytes32",
+        name: "userCommitment",
+        type: "bytes32",
+      },
+      {
+        internalType: "bool",
+        name: "useBlockHash",
+        type: "bool",
+      },
+    ],
+    name: "request",
+    outputs: [
+      {
+        internalType: "uint64",
+        name: "assignedSequenceNumber",
+        type: "uint64",
+      },
+    ],
+    stateMutability: "payable",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        internalType: "bytes32",
+        name: "userRandomNumber",
+        type: "bytes32",
+      },
+    ],
+    name: "requestWithCallback",
+    outputs: [
+      {
+        internalType: "uint64",
+        name: "assignedSequenceNumber",
+        type: "uint64",
+      },
+    ],
+    stateMutability: "payable",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        internalType: "uint64",
+        name: "sequenceNumber",
+        type: "uint64",
+      },
+      {
+        internalType: "bytes32",
+        name: "userRevelation",
+        type: "bytes32",
+      },
+      {
+        internalType: "bytes32",
+        name: "providerRevelation",
+        type: "bytes32",
+      },
+    ],
+    name: "reveal",
+    outputs: [
+      {
+        internalType: "bytes32",
+        name: "randomNumber",
+        type: "bytes32",
+      },
+    ],
+    stateMutability: "nonpayable",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        internalType: "uint64",
+        name: "sequenceNumber",
+        type: "uint64",
+      },
+      {
+        internalType: "bytes32",
+        name: "userRandomNumber",
+        type: "bytes32",
+      },
+      {
+        internalType: "bytes32",
+        name: "providerRevelation",
+        type: "bytes32",
+      },
+    ],
+    name: "revealWithCallback",
+    outputs: [],
+    stateMutability: "nonpayable",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "address",
+        name: "manager",
+        type: "address",
+      },
+    ],
+    name: "setFeeManager",
+    outputs: [],
+    stateMutability: "nonpayable",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "uint32",
+        name: "maxNumHashes",
+        type: "uint32",
+      },
+    ],
+    name: "setMaxNumHashes",
+    outputs: [],
+    stateMutability: "nonpayable",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "uint128",
+        name: "newFeeInWei",
+        type: "uint128",
+      },
+    ],
+    name: "setProviderFee",
+    outputs: [],
+    stateMutability: "nonpayable",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        internalType: "uint128",
+        name: "newFeeInWei",
+        type: "uint128",
+      },
+    ],
+    name: "setProviderFeeAsFeeManager",
+    outputs: [],
+    stateMutability: "nonpayable",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "bytes",
+        name: "newUri",
+        type: "bytes",
+      },
+    ],
+    name: "setProviderUri",
+    outputs: [],
+    stateMutability: "nonpayable",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "uint128",
+        name: "amount",
+        type: "uint128",
+      },
+    ],
+    name: "withdraw",
+    outputs: [],
+    stateMutability: "nonpayable",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "address",
+        name: "provider",
+        type: "address",
+      },
+      {
+        internalType: "uint128",
+        name: "amount",
+        type: "uint128",
+      },
+    ],
+    name: "withdrawAsFeeManager",
+    outputs: [],
+    stateMutability: "nonpayable",
+    type: "function",
+  },
+] as const;

+ 124 - 0
apps/entropy-debug/src/lib/revelation.ts

@@ -0,0 +1,124 @@
+import { createPublicClient, http, parseEventLogs, publicActions } from 'viem'
+import { EntropyAbi } from './EntropyAbi'
+import { EntropyDeployment, EntropyDeployments } from '../store/EntropyDeployments'
+
+interface Revelation {
+  value: {
+    data: string;
+  };
+}
+
+export async function requestCallback(txHash: string, chain: string): Promise<string> {
+  console.log("requestCallback", txHash, chain)
+  const deployment = EntropyDeployments[chain]
+  console.log("deployment", deployment)
+  if (!deployment) {
+    console.error("Deployment for chain not found", chain)
+    throw new Error(`Deployment for chain ${chain} not found`)
+  }
+
+  let provider: string
+  let sequenceNumber: bigint
+  let userRandomNumber: string
+
+  try {
+    ({ provider, sequenceNumber, userRandomNumber } = await fetchInfoFromTx(txHash, deployment))
+  } catch (error) {
+    console.error("Error fetching info from tx:", error)
+    throw new Error("We found an error message: " + error)
+  }
+
+  let revelation: string | Revelation
+  try {
+    revelation = await getRevelation(chain, Number(sequenceNumber))
+  } catch (error) {
+    console.error("Error fetching revelation:", error)
+    throw new Error("We found an error message: " + error)
+  }
+
+  console.log("revelation", revelation)
+
+  // It means the there is an error message
+  if (typeof revelation === "string") {
+    console.error("We found an error message: " + revelation)
+    throw new Error("We found an error message: " + revelation)
+  }
+
+  const message = `cast send ${deployment.address} 'revealWithCallback(address, uint64, bytes32, bytes32)' ${provider} ${sequenceNumber} ${userRandomNumber} ${revelation.value.data} -r ${deployment.rpc} --private-key <YOUR_PRIVATE_KEY>`
+  console.log("message", message)
+
+  return message
+}
+
+  export async function fetchInfoFromTx(txHash: string, deployment: EntropyDeployment) {
+    const publicClient = createPublicClient({
+      transport: http(deployment.rpc)
+    }).extend(publicActions)
+    if (!publicClient) {
+      throw new Error(`Error creating public client for ${deployment}`)
+    }
+
+    const receipt = await publicClient.getTransactionReceipt({
+      hash: txHash as `0x${string}`
+    })
+    if (!receipt) {
+      throw new Error(`Transaction receipt not found for ${txHash}`)
+    }
+    console.log("receipt: ", receipt)
+
+    const logs = parseEventLogs({
+      abi: EntropyAbi,
+      logs: receipt.logs,
+      eventName: "RequestedWithCallback"
+    })
+    if (!logs) {
+      throw new Error(`Error parsing logs for ${txHash}. Are you sure you send the requestCallback Transaction?`)
+    }
+    console.log("logs: ", logs)
+
+    if (logs.length === 0) {
+      throw new Error(`No logs found for ${txHash}. Are you sure you send the requestCallback Transaction?`)
+    }
+
+    const provider = logs[0].args.provider
+    const sequenceNumber = logs[0].args.sequenceNumber
+    const userRandomNumber = logs[0].args.userRandomNumber
+
+    return { provider, sequenceNumber, userRandomNumber }
+  }
+
+export async function getRevelation(chain: string, sequenceNumber: number) {
+  const deployment = EntropyDeployments[chain]
+  if (!deployment) {
+    throw new Error(`Deployment for chain ${chain} not found`)
+  }
+
+  let response: Response
+
+  try {
+      const isMainnet = deployment.network === "mainnet"
+      const baseUrl = isMainnet
+        ? "https://fortuna.dourolabs.app"
+        : "https://fortuna-staging.dourolabs.app"
+
+      response = await fetch(
+        `${baseUrl}/v1/chains/${chain}/revelations/${sequenceNumber}`,
+        {
+          headers: {
+            'Content-Type': 'application/json',
+          }
+        }
+      )
+    } catch (error) {
+      console.error("We found an error while fetching the revelation: " + error)
+      throw new Error("We found an error while fetching the revelation: " + error)
+    }
+
+  if (response.status.toString().startsWith("4") || response.status.toString().startsWith("5")) {
+    const errorMessage = await response.text()
+    console.error("The provider returned an error:", errorMessage)
+    throw new Error("The provider returned an error: " + errorMessage)
+  }
+
+  return await response.json()
+}

+ 11 - 0
apps/entropy-debug/src/lib/utils.ts

@@ -0,0 +1,11 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+  return twMerge(clsx(inputs));
+}
+
+export function isValidTxHash(hash: string) {
+  const cleanHash = hash.toLowerCase().replace("0x", "");
+  return /^[a-f0-9]{64}$/.test(cleanHash);
+}

+ 399 - 0
apps/entropy-debug/src/store/EntropyDeployments.tsx

@@ -0,0 +1,399 @@
+export interface EntropyDeployment {
+  address: string;
+  network: "mainnet" | "testnet";
+  explorer: string;
+  delay: string;
+  gasLimit: string;
+  rpc: string;
+  nativeCurrency: string;
+}
+
+export const EntropyDeployments: Record<string, EntropyDeployment> = {
+  blast: {
+    address: "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    network: "mainnet",
+    explorer: "https://blastscan.io/address/$ADDRESS",
+    delay: "1 block",
+    gasLimit: "500K",
+    rpc: "https://rpc.blast.io",
+    nativeCurrency: "ETH",
+  },
+  "lightlink-phoenix": {
+    address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603",
+    network: "mainnet",
+    explorer: "https://phoenix.lightlink.io/address/$ADDRESS",
+    delay: "1 block",
+    gasLimit: "500K",
+    rpc: "https://replicator.phoenix.lightlink.io/rpc/v1",
+    nativeCurrency: "ETH",
+  },
+  chiliz: {
+    address: "0x0708325268dF9F66270F1401206434524814508b",
+    network: "mainnet",
+    explorer: "https://scan.chiliz.com/address/$ADDRESS",
+    delay: "12 blocks",
+    gasLimit: "500K",
+    rpc: "https://rpc.ankr.com/chiliz",
+    nativeCurrency: "CHZ",
+  },
+  arbitrum: {
+    address: "0x7698E925FfC29655576D0b361D75Af579e20AdAc",
+    network: "mainnet",
+    explorer: "https://arbiscan.io/address/$ADDRESS",
+    delay: "6 blocks",
+    gasLimit: "2.5M",
+    rpc: "https://arb1.arbitrum.io/rpc",
+    nativeCurrency: "ETH",
+  },
+  optimism: {
+    address: "0xdF21D137Aadc95588205586636710ca2890538d5",
+    network: "mainnet",
+    explorer: "https://optimistic.etherscan.io/address/$ADDRESS",
+    delay: "2 blocks",
+    gasLimit: "500K",
+    rpc: "https://rpc.ankr.com/optimism",
+    nativeCurrency: "ETH",
+  },
+  mode: {
+    address: "0x8D254a21b3C86D32F7179855531CE99164721933",
+    network: "mainnet",
+    explorer: "https://explorer.mode.network/address/$ADDRESS",
+    delay: "2 blocks",
+    gasLimit: "500K",
+    rpc: "https://mainnet.mode.network/",
+    nativeCurrency: "ETH",
+  },
+  zetachain: {
+    address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320",
+    network: "mainnet",
+    explorer: "https://zetachain.blockscout.com/address/$ADDRESS",
+    delay: "0 block",
+    gasLimit: "500K",
+    rpc: "https://zetachain-evm.blockpi.network/v1/rpc/public",
+    nativeCurrency: "ZETA",
+  },
+  base: {
+    address: "0x6E7D74FA7d5c90FEF9F0512987605a6d546181Bb",
+    network: "mainnet",
+    explorer: "https://basescan.org/address/$ADDRESS",
+    delay: "1 block",
+    gasLimit: "500K",
+    rpc: "https://base.llamarpc.com",
+    nativeCurrency: "ETH",
+  },
+  "lightlink-pegasus": {
+    rpc: "https://replicator.pegasus.lightlink.io/rpc/v1",
+    network: "testnet",
+    delay: "",
+    address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a",
+    explorer: "https://pegasus.lightlink.io/address/$ADDRESS",
+    gasLimit: "500K",
+    nativeCurrency: "ETH",
+  },
+  "chiliz-spicy": {
+    rpc: "https://spicy-rpc.chiliz.com",
+    network: "testnet",
+    delay: "",
+    address: "0xD458261E832415CFd3BAE5E416FdF3230ce6F134",
+    explorer: "https://spicy-explorer.chiliz.com/address/$ADDRESS",
+    gasLimit: "500K",
+    nativeCurrency: "CHZ",
+  },
+  "conflux-espace-testnet": {
+    rpc: "https://evmtestnet.confluxrpc.com",
+    network: "testnet",
+    delay: "",
+    address: "0xdF21D137Aadc95588205586636710ca2890538d5",
+    explorer: "https://evmtestnet.confluxscan.io/address/$ADDRESS",
+    gasLimit: "500K",
+    nativeCurrency: "CFX",
+  },
+  "mode-sepolia": {
+    rpc: "https://sepolia.mode.network/",
+    network: "testnet",
+    delay: "",
+    address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603",
+    explorer: "https://sepolia.explorer.mode.network/address/$ADDRESS",
+    gasLimit: "500K",
+    nativeCurrency: "ETH",
+  },
+  "sei-evm-testnet": {
+    rpc: "https://evm-rpc-testnet.sei-apis.com",
+    network: "testnet",
+    delay: "",
+    address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320",
+    explorer: "https://seitrace.com/address/$ADDRESS?chain=atlantic-2",
+    gasLimit: "500K",
+    nativeCurrency: "SEI",
+  },
+  "arbitrum-sepolia": {
+    rpc: "https://sepolia-rollup.arbitrum.io/rpc",
+    network: "testnet",
+    delay: "",
+    address: "0x549Ebba8036Ab746611B4fFA1423eb0A4Df61440",
+    explorer: "https://sepolia.arbiscan.io/address/$ADDRESS",
+    gasLimit: "2.5M",
+    nativeCurrency: "ETH",
+  },
+  "fantom-sonic": {
+    rpc: "https://rpc.testnet.soniclabs.com",
+    network: "testnet",
+    delay: "",
+    address: "0x5124FAE0890dE83B3bb2cc30Bb3EDAfFc07Da744",
+    explorer: "https://testnet.soniclabs.com/address/$ADDRESS",
+    gasLimit: "500K",
+    nativeCurrency: "S",
+  },
+  "blast-testnet": {
+    rpc: "https://sepolia.blast.io",
+    network: "testnet",
+    delay: "",
+    address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603",
+    explorer: "https://testnet.blastscan.io/address/$ADDRESS",
+    gasLimit: "500K",
+    nativeCurrency: "ETH",
+  },
+  "optimism-sepolia": {
+    rpc: "https://api.zan.top/opt-sepolia",
+    network: "testnet",
+    delay: "",
+    address: "0x4821932D0CDd71225A6d914706A621e0389D7061",
+    explorer: "https://optimism-sepolia.blockscout.com/address/$ADDRESS",
+    gasLimit: "500K",
+    nativeCurrency: "ETH",
+  },
+  "base-sepolia": {
+    rpc: "https://sepolia.base.org",
+    network: "testnet",
+    delay: "",
+    address: "0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c",
+    explorer: "https://base-sepolia.blockscout.com/address/$ADDRESS",
+    gasLimit: "500K",
+    nativeCurrency: "ETH",
+  },
+  "berachain-testnet-v2": {
+    rpc: "https://evm-rpc-bera.rhino-apis.com/",
+    network: "testnet",
+    delay: "",
+    address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320",
+    explorer: "https://bartio.beratrail.io/address/$ADDRESS",
+    gasLimit: "500K",
+    nativeCurrency: "BERA",
+  },
+  "coredao-testnet": {
+    rpc: "https://rpc.test.btcs.network",
+    network: "testnet",
+    delay: "",
+    address: "0xf0a1b566B55e0A0CB5BeF52Eb2a57142617Bee67",
+    explorer: "https://scan.test.btcs.network/address/$ADDRESS",
+    gasLimit: "500K",
+    nativeCurrency: "tCORE",
+  },
+  "zetachain-testnet": {
+    rpc: "https://zetachain-athens-evm.blockpi.network/v1/rpc/public",
+    network: "testnet",
+    delay: "",
+    address: "0x4374e5a8b9C22271E9EB878A2AA31DE97DF15DAF",
+    explorer: "https://explorer.zetachain.com/address/$ADDRESS",
+    gasLimit: "500K",
+    nativeCurrency: "ZETA",
+  },
+  "taiko-hekla": {
+    rpc: "https://rpc.hekla.taiko.xyz/",
+    network: "testnet",
+    delay: "",
+    address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603",
+    explorer: "https://hekla.taikoscan.network/address/$ADDRESS",
+    gasLimit: "500K",
+    nativeCurrency: "ETH",
+  },
+  "orange-testnet": {
+    address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603",
+    explorer: "https://subnets-test.avax.network/orangetest/address/$ADDRESS",
+    delay: "",
+    gasLimit: "500K",
+    network: "testnet",
+    rpc: "https://subnets.avax.network/orangetest/testnet/rpc",
+    nativeCurrency: "JUICE",
+  },
+  "sei-evm-mainnet": {
+    address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603",
+    explorer: "https://seitrace.com/address/$ADDRESS?chain=pacific-1",
+    delay: "1 block",
+    gasLimit: "500K",
+    network: "mainnet",
+    rpc: "https://evm-rpc.sei-apis.com",
+    nativeCurrency: "SEI",
+  },
+  merlin: {
+    address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320",
+    explorer: "https://scan.merlinchain.io/address/$ADDRESS",
+    delay: "1 block",
+    gasLimit: "500K",
+    network: "mainnet",
+    rpc: "https://rpc.merlinchain.io",
+    nativeCurrency: "BTC",
+  },
+  "merlin-testnet": {
+    address: "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    explorer: "https://testnet-scan.merlinchain.io/address/$ADDRESS",
+    delay: "",
+    gasLimit: "500K",
+    network: "testnet",
+    rpc: "https://testnet-rpc.merlinchain.io/",
+    nativeCurrency: "BTC",
+  },
+  taiko: {
+    address: "0x26DD80569a8B23768A1d80869Ed7339e07595E85",
+    explorer: "https://taikoscan.io/address/$ADDRESS",
+    delay: "1 block",
+    gasLimit: "500K",
+    network: "mainnet",
+    rpc: "https://rpc.mainnet.taiko.xyz",
+    nativeCurrency: "ETH",
+  },
+  "etherlink-testnet": {
+    address: "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509",
+    explorer: "https://testnet.explorer.etherlink.com/address/$ADDRESS",
+    delay: "",
+    gasLimit: "15M",
+    network: "testnet",
+    rpc: "https://node.ghostnet.etherlink.com",
+    nativeCurrency: "XTZ",
+  },
+  etherlink: {
+    address: "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509",
+    explorer: "https://explorer.etherlink.com/address/$ADDRESS",
+    delay: "1 block",
+    gasLimit: "15M",
+    network: "mainnet",
+    rpc: "https://node.mainnet.etherlink.com/",
+    nativeCurrency: "XTZ",
+  },
+  kaia: {
+    address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320",
+    explorer: "https://kaiascan.io/address/$ADDRESS",
+    delay: "1 block",
+    gasLimit: "500K",
+    network: "mainnet",
+    rpc: "https://rpc.ankr.com/klaytn",
+    nativeCurrency: "KLAY",
+  },
+  "kaia-testnet": {
+    address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320",
+    explorer: "https://kairos.kaiascan.io/address/$ADDRESS",
+    delay: "",
+    gasLimit: "500K",
+    network: "testnet",
+    rpc: "https://rpc.ankr.com/klaytn_testnet",
+    nativeCurrency: "KLAY",
+  },
+  "tabi-testnet": {
+    address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320",
+    explorer: "https://testnet.tabiscan.com/address/$ADDRESS",
+    delay: "",
+    gasLimit: "500K",
+    network: "testnet",
+    rpc: "https://rpc-internal.testnet.tabichain.com/",
+    nativeCurrency: "TABI",
+  },
+  "b3-testnet": {
+    address: "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    explorer: "https://sepolia.explorer.b3.fun/address/$ADDRESS",
+    delay: "",
+    gasLimit: "500K",
+    network: "testnet",
+    rpc: "https://sepolia.b3.fun/http/",
+    nativeCurrency: "ETH",
+  },
+  "b3-mainnet": {
+    address: "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    explorer: "https://explorer.b3.fun/address/$ADDRESS",
+    delay: "1 block",
+    gasLimit: "500K",
+    network: "mainnet",
+    rpc: "https://mainnet-rpc.b3.fun/http",
+    nativeCurrency: "ETH",
+  },
+  "apechain-testnet": {
+    address: "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509",
+    explorer: "https://curtis.explorer.caldera.xyz/address/$ADDRESS",
+    delay: "",
+    gasLimit: "500K",
+    network: "testnet",
+    rpc: "https://curtis.rpc.caldera.xyz/http",
+    nativeCurrency: "APE",
+  },
+  "soneium-minato-testnet": {
+    address: "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509",
+    explorer: "https://explorer-testnet.soneium.org/address/$ADDRESS",
+    delay: "",
+    gasLimit: "500K",
+    network: "testnet",
+    rpc: "https://rpc.minato.soneium.org/",
+    nativeCurrency: "ETH",
+  },
+  sanko: {
+    address: "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    explorer: "https://explorer.sanko.xyz/address/$ADDRESS",
+    delay: "1 block",
+    gasLimit: "500K",
+    network: "mainnet",
+    rpc: "https://mainnet.sanko.xyz",
+    nativeCurrency: "DMT",
+  },
+  "sanko-testnet": {
+    address: "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    explorer: "https://sanko-arb-sepolia.explorer.caldera.xyz/address/$ADDRESS",
+    delay: "",
+    gasLimit: "500K",
+    network: "testnet",
+    rpc: "https://sanko-arb-sepolia.rpc.caldera.xyz/http",
+    nativeCurrency: "DMT",
+  },
+  apechain: {
+    address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320",
+    explorer: "https://apechain.calderaexplorer.xyz/address/$ADDRESS",
+    delay: "1 block",
+    gasLimit: "500K",
+    network: "mainnet",
+    rpc: "https://apechain.calderachain.xyz/http",
+    nativeCurrency: "APE",
+  },
+  "abstract-testnet": {
+    address: "0x858687fD592112f7046E394A3Bf10D0C11fF9e63",
+    explorer: "https://explorer.testnet.abs.xyz/address/$ADDRESS",
+    delay: "",
+    gasLimit: "500K",
+    network: "testnet",
+    rpc: "https://api.testnet.abs.xyz",
+    nativeCurrency: "ETH",
+  },
+  "fantom-sonic-devnet": {
+    address: "0xebe57e8045f2f230872523bbff7374986e45c486",
+    explorer: "https://blaze.soniclabs.com/address/$ADDRESS",
+    delay: "",
+    gasLimit: "500K",
+    network: "testnet",
+    rpc: "https://rpc.blaze.soniclabs.com",
+    nativeCurrency: "S",
+  },
+  "unichain-sepolia": {
+    address: "0x8D254a21b3C86D32F7179855531CE99164721933",
+    explorer: "https://unichain-sepolia.blockscout.com/address/$ADDRESS",
+    delay: "",
+    gasLimit: "500K",
+    network: "testnet",
+    rpc: "https://sepolia.unichain.org",
+    nativeCurrency: "ETH",
+  },
+  "sonic-fantom": {
+    address: "0x36825bf3fbdf5a29e2d5148bfe7dcf7b5639e320",
+    explorer: "https://monitor.soniclabs.com/address/$ADDRESS",
+    delay: "1 block",
+    gasLimit: "500K",
+    network: "mainnet",
+    rpc: "https://rpc.soniclabs.com",
+    nativeCurrency: "S",
+  },
+};

+ 8 - 0
apps/entropy-debug/tsconfig.json

@@ -0,0 +1,8 @@
+{
+  "extends": "@cprussin/tsconfig/nextjs.json",
+  "include": ["svg.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+  "exclude": ["node_modules"],
+  "compilerOptions": {
+    "jsx": "preserve"
+  }
+}

File diff suppressed because it is too large
+ 551 - 20
pnpm-lock.yaml


Some files were not shown because too many files changed in this diff