Merge pull request #136 from eqlabs/feat/hooks-v2-preparations

Feat/hooks v2 preparations
This commit is contained in:
Valtteri Karesto
2022-03-25 09:48:19 +02:00
committed by GitHub
26 changed files with 3363 additions and 2078 deletions

View File

@@ -2,4 +2,8 @@ NEXTAUTH_URL=https://example.com
GITHUB_SECRET=""
GITHUB_ID=""
NEXT_PUBLIC_COMPILE_API_ENDPOINT="http://localhost:9000/api/build"
NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT="ws://localhost:9000/language-server/c"
NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT="ws://localhost:9000/language-server/c"
NEXT_PUBLIC_TESTNET_URL="hooks-testnet-v2.xrpl-labs.com"
NEXT_PUBLIC_DEBUG_STREAM_URL="hooks-testnet-v2-debugstream.xrpl-labs.com"
NEXT_PUBLIC_EXPLORER_URL="hooks-testnet-v2-explorer.xrpl-labs.com"
NEXT_PUBLIC_SITE_URL=http://localhost:3000

View File

@@ -1,11 +1,11 @@
import toast from "react-hot-toast";
import { useSnapshot } from "valtio";
import { ArrowSquareOut, Copy, Wallet, X } from "phosphor-react";
import { ArrowSquareOut, Copy, Trash, Wallet, X } from "phosphor-react";
import React, { useEffect, useState, FC } from "react";
import Dinero from "dinero.js";
import Button from "./Button";
import { addFaucetAccount, deployHook, importAccount } from "../state/actions";
import { addFaucetAccount, importAccount } from "../state/actions";
import state from "../state";
import Box from "./Box";
import { Container, Heading, Stack, Text, Flex } from ".";
@@ -19,6 +19,7 @@ import {
} from "./Dialog";
import { css } from "../stitches.config";
import { Input } from "./Input";
import truncate from "../utils/truncate";
const labelStyle = css({
color: "$mauve10",
@@ -26,6 +27,10 @@ const labelStyle = css({
fontSize: "10px",
mb: "$0.5",
});
import transactionsData from "../content/transactions.json";
import { SetHookDialog } from "./SetHookDialog";
import { addFunds } from "../state/actions/addFaucetAccount";
import { deleteHook } from "../state/actions/deployHook";
export const AccountDialog = ({
activeAccountAddress,
@@ -86,6 +91,22 @@ export const AccountDialog = ({
}}
>
<Wallet size="15px" /> {activeAccount?.name}
<DialogClose asChild>
<Button
size="xs"
outline
css={{ ml: "auto", mr: "$9" }}
tabIndex={-1}
onClick={() => {
const index = state.accounts.findIndex(
(acc) => acc.address === activeAccount?.address
);
state.accounts.splice(index, 1);
}}
>
Delete Account <Trash size="15px" />
</Button>
</DialogClose>
</DialogTitle>
<DialogDescription as="div" css={{ fontFamily: "$monospace" }}>
<Stack css={{ display: "flex", flexDirection: "column", gap: "$3" }}>
@@ -163,6 +184,8 @@ export const AccountDialog = ({
<Text
css={{
fontFamily: "$monospace",
display: "flex",
alignItems: "center",
}}
>
{Dinero({
@@ -175,11 +198,26 @@ export const AccountDialog = ({
currency: "XRP",
currencyDisplay: "name",
})}
<Button
css={{
fontFamily: "$monospace",
lineHeight: 2,
mt: "2px",
ml: "$3",
}}
ghost
size="xs"
onClick={() => {
addFunds(activeAccount?.address || "");
}}
>
Add Funds
</Button>
</Text>
</Flex>
<Flex css={{ marginLeft: "auto" }}>
<a
href={`https://hooks-testnet-explorer.xrpl-labs.com/${activeAccount?.address}`}
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${activeAccount?.address}`}
target="_blank"
rel="noreferrer noopener"
>
@@ -201,9 +239,25 @@ export const AccountDialog = ({
fontFamily: "$monospace",
}}
>
{activeAccount && activeAccount.hooks.length}
{activeAccount && activeAccount.hooks.length > 0
? activeAccount.hooks.map((i) => truncate(i, 12)).join(",")
: ""}
</Text>
</Flex>
{activeAccount && activeAccount?.hooks?.length > 0 && (
<Flex css={{ marginLeft: "auto" }}>
<Button
size="xs"
outline
css={{ mt: "$3", mr: "$1", ml: "auto" }}
onClick={() => {
deleteHook(activeAccount);
}}
>
Delete Hook <Trash size="15px" />
</Button>
</Flex>
)}
</Flex>
</Stack>
</DialogDescription>
@@ -233,7 +287,7 @@ const Accounts: FC<AccountProps> = (props) => {
if (snap.clientStatus === "online") {
const requests = snap.accounts.map((acc) =>
snap.client?.send({
id: acc.address,
id: `hooks-builder-req-info-${acc.address}`,
command: "account_info",
account: acc.address,
})
@@ -253,7 +307,7 @@ const Accounts: FC<AccountProps> = (props) => {
});
const objectRequests = snap.accounts.map((acc) => {
return snap.client?.send({
id: `${acc.address}-hooks`,
id: `hooks-builder-req-objects-${acc.address}`,
command: "account_objects",
account: acc.address,
});
@@ -265,9 +319,10 @@ const Accounts: FC<AccountProps> = (props) => {
(acc) => acc.address === address
);
if (accountToUpdate) {
accountToUpdate.hooks = res.account_objects
.filter((ac: any) => ac?.LedgerEntryType === "Hook")
.map((oo: any) => oo.HookHash);
accountToUpdate.hooks =
res.account_objects
.find((ac: any) => ac?.LedgerEntryType === "Hook")
?.Hooks?.map((oo: any) => oo.Hook.HookHash) || [];
}
});
}
@@ -276,7 +331,7 @@ const Accounts: FC<AccountProps> = (props) => {
let fetchAccountInfoInterval: NodeJS.Timer;
if (snap.clientStatus === "online") {
fetchAccInfo();
fetchAccountInfoInterval = setInterval(() => fetchAccInfo(), 2000);
fetchAccountInfoInterval = setInterval(() => fetchAccInfo(), 10000);
}
return () => {
@@ -338,7 +393,6 @@ const Accounts: FC<AccountProps> = (props) => {
fontSize: "13px",
wordWrap: "break-word",
fontWeight: "$body",
fontFamily: "$monospace",
gap: 0,
height: "calc(100% - 52px)",
flexWrap: "nowrap",
@@ -392,29 +446,12 @@ const Accounts: FC<AccountProps> = (props) => {
</Box>
{!props.hideDeployBtn && (
<div
className="hook-deploy-button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<Button
css={{ ml: "auto" }}
size="xs"
uppercase
isLoading={account.isLoading}
disabled={
account.isLoading ||
!snap.files.filter((file) => file.compiledWatContent)
.length
}
variant="secondary"
onClick={(e) => {
e.stopPropagation();
deployHook(account);
}}
>
Deploy
</Button>
<SetHookDialog account={account} />
</div>
)}
</Flex>
@@ -436,6 +473,11 @@ const Accounts: FC<AccountProps> = (props) => {
);
};
export const transactionsOptions = transactionsData.map((tx) => ({
value: tx.TransactionType,
label: tx.TransactionType,
}));
const ImportAccountDialog = () => {
const [value, setValue] = useState("");
return (

View File

@@ -87,7 +87,8 @@ export const StyledButton = styled("button", {
boxShadow: "inset 0 0 0 1px $colors$mauve11",
},
"&:focus": {
boxShadow: "inset 0 0 0 1px $colors$mauve12, inset 0 0 0 2px $colors$mauve12",
boxShadow:
"inset 0 0 0 1px $colors$mauve12, inset 0 0 0 2px $colors$mauve12",
},
'&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]':
{
@@ -141,6 +142,29 @@ export const StyledButton = styled("button", {
boxShadow: "inset 0 0 0 1px $colors$purple8",
},
},
destroy: {
backgroundColor: `$red9`,
boxShadow: "inset 0 0 0 1px $colors$red9",
color: "$white",
"@hover": {
"&:hover": {
backgroundColor: "$red10",
boxShadow: "inset 0 0 0 1px $colors$red11",
},
},
"&:active": {
backgroundColor: "$red8",
boxShadow: "inset 0 0 0 1px $colors$red8",
},
"&:focus": {
boxShadow: "inset 0 0 0 2px $colors$red12",
},
'&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]':
{
backgroundColor: "$mauve4",
boxShadow: "inset 0 0 0 1px $colors$red8",
},
},
},
muted: {
true: {
@@ -236,16 +260,21 @@ export const StyledButton = styled("button", {
},
});
const CustomButton: React.FC<React.ComponentProps<typeof StyledButton> & { as?: string }> =
React.forwardRef(({ children, as = "button", ...rest }, ref) => (
// @ts-expect-error
<StyledButton {...rest} ref={ref} as={as}>
<Flex as="span" css={{ gap: "$2", alignItems: "center" }} className="button-content">
{children}
</Flex>
{rest.isLoading && <Spinner css={{ position: "absolute" }} />}
</StyledButton>
));
const CustomButton: React.FC<
React.ComponentProps<typeof StyledButton> & { as?: string }
> = React.forwardRef(({ children, as = "button", ...rest }, ref) => (
// @ts-expect-error
<StyledButton {...rest} ref={ref} as={as}>
<Flex
as="span"
css={{ gap: "$2", alignItems: "center" }}
className="button-content"
>
{children}
</Flex>
{rest.isLoading && <Spinner css={{ position: "absolute" }} />}
</StyledButton>
));
CustomButton.displayName = "CustomButton";

View File

@@ -20,7 +20,7 @@ const DebugStream = () => {
const { selectedAccount, logs, socket } = useSnapshot(streamState);
const { accounts } = useSnapshot(state);
const accountOptions = accounts.map(acc => ({
const accountOptions = accounts.map((acc) => ({
label: acc.name,
value: acc.address,
}));
@@ -33,7 +33,7 @@ const DebugStream = () => {
options={accountOptions}
hideSelectedOptions
value={selectedAccount}
onChange={acc => (streamState.selectedAccount = acc as any)}
onChange={(acc) => (streamState.selectedAccount = acc as any)}
css={{ width: "100%" }}
/>
</>
@@ -57,7 +57,6 @@ const DebugStream = () => {
const jsonData = extracted
? JSON.stringify(extracted.result, null, 2)
: undefined;
return {
type: "log",
message,
@@ -73,7 +72,7 @@ const DebugStream = () => {
socket?.close();
streamState.socket = ref(
new WebSocket(
`wss://hooks-testnet-debugstream.xrpl-labs.com/${account}`
`wss://${process.env.NEXT_PUBLIC_DEBUG_STREAM_URL}/${account}`
)
);
} else if (!account && socket) {
@@ -109,7 +108,17 @@ const DebugStream = () => {
};
const onMessage = (event: any) => {
if (!event.data) return;
streamState.logs.push(prepareLog(event.data));
const log = prepareLog(event.data);
// Filter out account_info and account_objects requests
try {
const parsed = JSON.parse(log.jsonData);
if (parsed.id.includes("hooks-builder-req")) {
return;
}
} catch (err) {
// Lets just skip if we cannot parse the message
}
return streamState.logs.push(log);
};
socket.addEventListener("open", onOpen);

View File

@@ -6,21 +6,28 @@ import * as DialogPrimitive from "@radix-ui/react-dialog";
import { styled } from "../stitches.config";
const overlayShow = keyframes({
"0%": { opacity: 0 },
"0%": { opacity: 0.01 },
"100%": { opacity: 1 },
});
const contentShow = keyframes({
"0%": { opacity: 0, transform: "translate(-50%, -48%) scale(.96)" },
"100%": { opacity: 1, transform: "translate(-50%, -50%) scale(1)" },
"0%": { opacity: 0.01 },
"100%": { opacity: 1 },
});
const StyledOverlay = styled(DialogPrimitive.Overlay, {
zIndex: 1000,
zIndex: 9999,
backgroundColor: blackA.blackA9,
position: "fixed",
inset: 0,
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "grid",
placeItems: "center",
overflowY: "auto",
"@media (prefers-reduced-motion: no-preference)": {
animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
animation: `${overlayShow} 250ms cubic-bezier(0.16, 1, 0.3, 1)`,
},
".dark &": {
backgroundColor: blackA.blackA11,
@@ -32,15 +39,12 @@ const StyledContent = styled(DialogPrimitive.Content, {
backgroundColor: "$mauve2",
color: "$mauve12",
borderRadius: "$md",
position: "relative",
boxShadow:
"0px 10px 38px -5px rgba(22, 23, 24, 0.25), 0px 10px 20px -5px rgba(22, 23, 24, 0.2)",
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "90vw",
maxWidth: "450px",
maxHeight: "85vh",
// maxHeight: "85vh",
padding: 25,
"@media (prefers-reduced-motion: no-preference)": {
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
@@ -55,10 +59,9 @@ const StyledContent = styled(DialogPrimitive.Content, {
const Content: React.FC<{ css?: Stiches.CSS }> = ({ css, children }) => {
return (
<div>
<StyledOverlay />
<StyledOverlay>
<StyledContent css={css}>{children}</StyledContent>
</div>
</StyledOverlay>
);
};
@@ -83,3 +86,4 @@ export const DialogContent = Content;
export const DialogTitle = StyledTitle;
export const DialogDescription = StyledDescription;
export const DialogClose = DialogPrimitive.Close;
export const DialogPortal = DialogPrimitive.Portal;

View File

@@ -1,3 +1,4 @@
import React from "react";
import { styled } from "../stitches.config";
export const Input = styled("input", {
@@ -148,4 +149,10 @@ export const Input = styled("input", {
},
});
export default Input;
// eslint-disable-next-line react/display-name
const ReffedInput = React.forwardRef<
HTMLInputElement,
React.ComponentProps<typeof Input>
>((props, ref) => <Input {...props} ref={ref} />);
export default ReffedInput;

View File

@@ -1,4 +1,11 @@
import { useRef, useLayoutEffect, ReactNode, FC, useState, useCallback } from "react";
import {
useRef,
useLayoutEffect,
ReactNode,
FC,
useState,
useCallback,
} from "react";
import { Notepad, Prohibit } from "phosphor-react";
import useStayScrolled from "react-stay-scrolled";
import NextLink from "next/link";
@@ -19,7 +26,14 @@ interface ILogBox {
enhanced?: boolean;
}
const LogBox: FC<ILogBox> = ({ title, clearLog, logs, children, renderNav, enhanced }) => {
const LogBox: FC<ILogBox> = ({
title,
clearLog,
logs,
children,
renderNav,
enhanced,
}) => {
const logRef = useRef<HTMLPreElement>(null);
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
@@ -148,11 +162,11 @@ export const Log: FC<ILog> = ({
(str?: string): ReactNode => {
if (!str || !accounts.length) return null;
const pattern = `(${accounts.map(acc => acc.address).join("|")})`;
const pattern = `(${accounts.map((acc) => acc.address).join("|")})`;
const res = regexifyString({
pattern: new RegExp(pattern, "gim"),
decorator: (match, idx) => {
const name = accounts.find(acc => acc.address === match)?.name;
const name = accounts.find((acc) => acc.address === match)?.name;
return (
<Link
key={match + idx}
@@ -183,7 +197,11 @@ export const Log: FC<ILog> = ({
activeAccountAddress={dialogAccount}
/>
<LogText variant={type}>
{timestamp && <Text muted monospace>{timestamp} </Text>}
{timestamp && (
<Text muted monospace>
{timestamp}{" "}
</Text>
)}
<Pre>{message} </Pre>
{link && (
<NextLink href={link} shallow passHref>

View File

@@ -128,7 +128,7 @@ const Navigation = () => {
</DialogTrigger>
<DialogContent
css={{
maxWidth: "100%",
maxWidth: "1080px",
width: "80vw",
height: "80%",
backgroundColor: "$mauve1 !important",
@@ -255,66 +255,67 @@ const Navigation = () => {
</Flex>
</DialogDescription>
</Flex>
<Flex
css={{
display: "grid",
gridTemplateColumns: "1fr",
gridTemplateRows: "max-content",
flex: 1,
p: "$7",
gap: "$3",
alignItems: "flex-start",
flexWrap: "wrap",
backgroundColor: "$mauve1",
"@md": {
gridTemplateColumns: "1fr 1fr 1fr",
<div>
<Flex
css={{
display: "grid",
gridTemplateColumns: "1fr",
gridTemplateRows: "max-content",
},
}}
>
<PanelBox
as="a"
href={`/develop/${templateFileIds.starter}`}
flex: 1,
p: "$7",
gap: "$3",
alignItems: "normal",
flexWrap: "wrap",
backgroundColor: "$mauve1",
"@md": {
gridTemplateColumns: "1fr 1fr 1fr",
gridTemplateRows: "max-content",
},
}}
>
<Heading>Starter</Heading>
<Text>
Just a basic starter with essential imports
</Text>
</PanelBox>
<PanelBox
as="a"
href={`/develop/${templateFileIds.firewall}`}
>
<Heading>Firewall</Heading>
<Text>
This Hook essentially checks a blacklist of accounts
</Text>
</PanelBox>
<PanelBox
as="a"
href={`/develop/${templateFileIds.notary}`}
>
<Heading>Notary</Heading>
<Text>
Collecting signatures for multi-sign transactions
</Text>
</PanelBox>
<PanelBox
as="a"
href={`/develop/${templateFileIds.carbon}`}
>
<Heading>Carbon</Heading>
<Text>Send a percentage of sum to an address</Text>
</PanelBox>
<PanelBox
as="a"
href={`/develop/${templateFileIds.peggy}`}
>
<Heading>Peggy</Heading>
<Text>An oracle based stable coin hook</Text>
</PanelBox>
</Flex>
<PanelBox
as="a"
href={`/develop/${templateFileIds.starter}`}
>
<Heading>Starter</Heading>
<Text>
Just a basic starter with essential imports
</Text>
</PanelBox>
<PanelBox
as="a"
href={`/develop/${templateFileIds.firewall}`}
>
<Heading>Firewall</Heading>
<Text>
This Hook essentially checks a blacklist of accounts
</Text>
</PanelBox>
<PanelBox
as="a"
href={`/develop/${templateFileIds.notary}`}
>
<Heading>Notary</Heading>
<Text>
Collecting signatures for multi-sign transactions
</Text>
</PanelBox>
<PanelBox
as="a"
href={`/develop/${templateFileIds.carbon}`}
>
<Heading>Carbon</Heading>
<Text>Send a percentage of sum to an address</Text>
</PanelBox>
<PanelBox
as="a"
href={`/develop/${templateFileIds.peggy}`}
>
<Heading>Peggy</Heading>
<Text>An oracle based stable coin hook</Text>
</PanelBox>
</Flex>
</div>
</Flex>
<DialogClose asChild>
<Box

View File

@@ -1,56 +1,152 @@
import { FC } from "react";
import { mauve, mauveDark } from "@radix-ui/colors";
import { forwardRef } from "react";
import { mauve, mauveDark, purple, purpleDark } from "@radix-ui/colors";
import { useTheme } from "next-themes";
import { styled } from '../stitches.config';
import dynamic from 'next/dynamic';
import { styled } from "../stitches.config";
import dynamic from "next/dynamic";
import type { Props } from "react-select";
const SelectInput = dynamic(() => import("react-select"), { ssr: false });
const Select: FC<Props> = props => {
// eslint-disable-next-line react/display-name
const Select = forwardRef<any, Props>((props, ref) => {
const { theme } = useTheme();
const isDark = theme === "dark";
const colors: any = {
// primary: pink.pink9,
active: isDark ? purpleDark.purple9 : purple.purple9,
activeLight: isDark ? purpleDark.purple5 : purple.purple5,
primary: isDark ? mauveDark.mauve4 : mauve.mauve4,
secondary: isDark ? mauveDark.mauve8 : mauve.mauve8,
background: isDark ? "rgb(10, 10, 10)" : "rgb(245, 245, 245)",
background: isDark ? mauveDark.mauve4 : mauve.mauve4,
searchText: isDark ? mauveDark.mauve12 : mauve.mauve12,
bg: isDark ? mauveDark.mauve1 : mauve.mauve1,
dropDownBg: isDark ? mauveDark.mauve5 : mauve.mauve2,
mauve4: isDark ? mauveDark.mauve4 : mauve.mauve4,
mauve5: isDark ? mauveDark.mauve5 : mauve.mauve5,
mauve8: isDark ? mauveDark.mauve8 : mauve.mauve8,
mauve9: isDark ? mauveDark.mauve9 : mauve.mauve9,
mauve12: isDark ? mauveDark.mauve12 : mauve.mauve12,
border: isDark ? mauveDark.mauve10 : mauve.mauve10,
placeholder: isDark ? mauveDark.mauve11 : mauve.mauve11,
};
colors.outline = colors.background;
colors.selected = colors.secondary;
return (
<SelectInput
menuPosition="fixed"
theme={theme => ({
...theme,
spacing: {
...theme.spacing,
controlHeight: 30
ref={ref}
menuPosition={props.menuPosition || "fixed"}
styles={{
container: (provided) => {
return {
...provided,
position: "relative",
};
},
colors: {
primary: colors.selected,
primary25: colors.primary,
primary50: colors.primary,
primary75: colors.primary,
danger: colors.primary,
dangerLight: colors.primary,
neutral0: colors.background,
neutral5: colors.primary,
neutral10: colors.primary,
neutral20: colors.outline,
neutral30: colors.primary,
neutral40: colors.primary,
neutral50: colors.placeholder,
neutral60: colors.primary,
neutral70: colors.primary,
neutral80: colors.searchText,
neutral90: colors.primary,
singleValue: (provided) => ({
...provided,
color: colors.mauve12,
}),
menu: (provided) => ({
...provided,
backgroundColor: colors.dropDownBg,
}),
control: (provided, state) => {
return {
...provided,
border: "0px",
backgroundColor: colors.mauve4,
boxShadow: `0 0 0 1px ${
state.isFocused ? colors.border : colors.secondary
}`,
};
},
})}
input: (provided) => {
return {
...provided,
color: "$text",
};
},
multiValue: (provided) => {
return {
...provided,
backgroundColor: colors.mauve8,
};
},
multiValueLabel: (provided) => {
return {
...provided,
color: colors.mauve12,
};
},
multiValueRemove: (provided) => {
return {
...provided,
":hover": {
background: colors.mauve9,
},
};
},
option: (provided, state) => {
return {
...provided,
color: colors.searchText,
backgroundColor:
state.isSelected || state.isFocused
? colors.activeLight
: colors.dropDownBg,
":hover": {
backgroundColor: colors.active,
color: "#ffffff",
},
":selected": {
backgroundColor: "red",
},
};
},
indicatorSeparator: (provided) => {
return {
...provided,
backgroundColor: colors.secondary,
};
},
dropdownIndicator: (provided, state) => {
return {
...provided,
color: state.isFocused ? colors.border : colors.secondary,
":hover": {
color: colors.border,
},
};
},
}}
// theme={(theme) => ({
// ...theme,
// spacing: {
// ...theme.spacing,
// controlHeight: 30,
// },
// colors: {
// primary: colors.selected,
// primary25: colors.active,
// primary50: colors.primary,
// primary75: colors.primary,
// danger: colors.primary,
// dangerLight: colors.primary,
// neutral0: colors.background,
// neutral5: colors.primary,
// neutral10: colors.primary,
// neutral20: colors.outline,
// neutral30: colors.primary,
// neutral40: colors.primary,
// neutral50: colors.placeholder,
// neutral60: colors.primary,
// neutral70: colors.primary,
// neutral80: colors.searchText,
// neutral90: colors.primary,
// },
// })}
{...props}
/>
);
};
});
export default styled(Select, {});

View File

@@ -0,0 +1,257 @@
import React, { useState } from "react";
import { Plus, Trash, X } from "phosphor-react";
import Button from "./Button";
import Box from "./Box";
import { Stack, Flex, Select } from ".";
import {
Dialog,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose,
DialogTrigger,
} from "./Dialog";
import { Input } from "./Input";
import {
Controller,
SubmitHandler,
useFieldArray,
useForm,
} from "react-hook-form";
import { TTS, tts } from "../utils/hookOnCalculator";
import { deployHook } from "../state/actions";
import type { IAccount } from "../state";
import { useSnapshot } from "valtio";
import state from "../state";
import toast from "react-hot-toast";
const transactionOptions = Object.keys(tts).map((key) => ({
label: key,
value: key as keyof TTS,
}));
export type SetHookData = {
Invoke: {
value: keyof TTS;
label: string;
}[];
HookParameters: {
HookParameter: {
HookParameterName: string;
HookParameterValue: string;
};
}[];
// HookGrants: {
// HookGrant: {
// Authorize: string;
// HookHash: string;
// };
// }[];
};
export const SetHookDialog: React.FC<{ account: IAccount }> = ({ account }) => {
const snap = useSnapshot(state);
const [isSetHookDialogOpen, setIsSetHookDialogOpen] = useState(false);
const {
register,
handleSubmit,
control,
// formState: { errors },
} = useForm<SetHookData>();
const { fields, append, remove } = useFieldArray({
control,
name: "HookParameters", // unique name for your Field Array
});
// const {
// fields: grantFields,
// append: grantAppend,
// remove: grantRemove,
// } = useFieldArray({
// control,
// name: "HookGrants", // unique name for your Field Array
// });
if (!account) {
return null;
}
const onSubmit: SubmitHandler<SetHookData> = async (data) => {
const currAccount = state.accounts.find(
(acc) => acc.address === account.address
);
if (currAccount) currAccount.isLoading = true;
const res = await deployHook(account, data);
if (currAccount) currAccount.isLoading = false;
if (res && res.engine_result === "tesSUCCESS") {
toast.success("Transaction succeeded!");
return setIsSetHookDialogOpen(false);
}
toast.error(`Transaction failed! (${res?.engine_result_message})`);
};
return (
<Dialog open={isSetHookDialogOpen} onOpenChange={setIsSetHookDialogOpen}>
<DialogTrigger asChild>
<Button
ghost
size="xs"
uppercase
variant={"secondary"}
disabled={
account.isLoading ||
!snap.files.filter((file) => file.compiledWatContent).length
}
>
Set Hook
</Button>
</DialogTrigger>
<DialogContent>
<form onSubmit={handleSubmit(onSubmit)}>
<DialogTitle>Deploy configuration</DialogTitle>
<DialogDescription as="div">
<Stack css={{ width: "100%", flex: 1 }}>
<Box css={{ width: "100%" }}>
<label>Invoke on transactions</label>
<Controller
name="Invoke"
control={control}
defaultValue={transactionOptions.filter(
(to) => to.label === "ttPAYMENT"
)}
render={({ field }) => (
<Select
{...field}
closeMenuOnSelect={false}
isMulti
menuPosition="fixed"
options={transactionOptions}
/>
)}
/>
</Box>
<Box css={{ width: "100%" }}>
<label style={{ marginBottom: "10px", display: "block" }}>
Hook parameters
</label>
<Stack>
{fields.map((field, index) => (
<Stack key={field.id}>
<Input
// important to include key with field's id
placeholder="Parameter name"
{...register(
`HookParameters.${index}.HookParameter.HookParameterName`
)}
/>
<Input
placeholder="Parameter value"
{...register(
`HookParameters.${index}.HookParameter.HookParameterValue`
)}
/>
<Button onClick={() => remove(index)} variant="destroy">
<Trash weight="regular" size="16px" />
</Button>
</Stack>
))}
<Button
outline
fullWidth
type="button"
onClick={() =>
append({
HookParameter: {
HookParameterName: "",
HookParameterValue: "",
},
})
}
>
<Plus size="16px" />
Add Hook Parameter
</Button>
</Stack>
</Box>
{/* <Box css={{ width: "100%" }}>
<label style={{ marginBottom: "10px", display: "block" }}>
Hook Grants
</label>
<Stack>
{grantFields.map((field, index) => (
<Stack key={field.id}>
<Input
// important to include key with field's id
placeholder="Authorize"
{...register(
`HookGrants.${index}.HookGrant.Authorize`,
{ minLength: 5 }
)}
/>
<Input
placeholder="HookHash"
{...register(`HookGrants.${index}.HookGrant.HookHash`, {
minLength: 64,
maxLength: 64,
})}
/>
<Button
onClick={() => grantRemove(index)}
variant="destroy"
>
<Trash weight="regular" size="16px" />
</Button>
</Stack>
))}
<Button
outline
fullWidth
type="button"
onClick={() =>
grantAppend({
HookGrant: {
Authorize: "",
HookHash: "",
},
})
}
>
<Plus size="16px" />
Add Hook Grant
</Button>
</Stack>
</Box> */}
</Stack>
</DialogDescription>
<Flex
css={{
marginTop: 25,
justifyContent: "flex-end",
gap: "$3",
}}
>
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
{/* <DialogClose asChild> */}
<Button
variant="primary"
type="submit"
isLoading={account.isLoading}
>
Set Hook
</Button>
{/* </DialogClose> */}
</Flex>
<DialogClose asChild>
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
<X size="20px" />
</Box>
</DialogClose>
</form>
</DialogContent>
</Dialog>
);
};
export default SetHookDialog;

1
next-env.d.ts vendored
View File

@@ -1,5 +1,4 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited

View File

@@ -27,6 +27,7 @@
"javascript-time-ago": "^2.3.11",
"jszip": "^3.7.1",
"lodash.uniqby": "^4.7.0",
"lodash.xor": "^4.5.0",
"monaco-editor": "^0.30.1",
"next": "^12.0.4",
"next-auth": "^4.0.0-beta.5",
@@ -40,6 +41,7 @@
"re-resizable": "^6.9.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-hook-form": "^7.28.0",
"react-hot-keys": "^2.7.1",
"react-hot-toast": "^2.1.1",
"react-new-window": "^0.2.1",
@@ -53,13 +55,14 @@
"vscode-languageserver": "^7.0.0",
"vscode-uri": "^3.0.2",
"wabt": "1.0.16",
"xrpl-accountlib": "^1.2.3",
"xrpl-client": "^1.9.3"
"xrpl-accountlib": "^1.3.2",
"xrpl-client": "^1.9.4"
},
"devDependencies": {
"@types/dinero.js": "^1.9.0",
"@types/file-saver": "^2.0.4",
"@types/lodash.uniqby": "^4.7.6",
"@types/lodash.xor": "^4.5.6",
"@types/pako": "^1.0.2",
"@types/react": "17.0.31",
"eslint": "7.32.0",
@@ -67,4 +70,4 @@
"raw-loader": "^4.0.2",
"typescript": "4.4.4"
}
}
}

View File

@@ -112,16 +112,6 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
content="#FDFCFD"
media="(prefers-color-scheme: light)"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin=""
/>
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital@0;1&family=Work+Sans:wght@400;600;700&display=swap"
rel="stylesheet"
/>
</Head>
<IdProvider>
<SessionProvider session={session}>

View File

@@ -24,6 +24,16 @@ class MyDocument extends Document {
id="stitches"
dangerouslySetInnerHTML={{ __html: getCssText() }}
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin=""
/>
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital@0;1&family=Work+Sans:wght@400;600;700&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />

View File

@@ -4,7 +4,9 @@ import { NextResponse as Response } from 'next/server';
export default function middleware(req: NextRequest, ev: NextFetchEvent) {
if (req.nextUrl.pathname === "/") {
return Response.redirect("/develop");
const url = req.nextUrl.clone();
url.pathname = '/develop';
return Response.redirect(url);
}
}

View File

@@ -21,8 +21,15 @@ export default async function handler(
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed!' })
}
const { account } = req.query;
const ip = Array.isArray(req?.headers?.["x-real-ip"]) ? req?.headers?.["x-real-ip"][0] : req?.headers?.["x-real-ip"];
try {
const response = await fetch('https://hooks-testnet.xrpl-labs.com/newcreds', { method: 'POST' });
const response = await fetch(`https://${process.env.NEXT_PUBLIC_TESTNET_URL}/newcreds?account=${account ? account : ''}`, {
method: 'POST',
headers: {
'x-forwarded-for': ip || '',
},
});
const json: Faucet | ErrorResponse = await response.json();
if ("error" in json) {
return res.status(429).json(json)

View File

@@ -4,9 +4,15 @@ import { FC, useCallback, useEffect, useState } from "react";
import Split from "react-split";
import { useSnapshot } from "valtio";
import {
Box, Button, Container,
Flex, Input,
Select, Tab, Tabs, Text
Box,
Button,
Container,
Flex,
Input,
Select,
Tab,
Tabs,
Text,
} from "../../components";
import transactionsData from "../../content/transactions.json";
import state from "../../state";
@@ -183,13 +189,23 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
return (
<Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
<Container
css={{ p: "$3 0", fontSize: "$sm", height: "calc(100% - 28px)" }}
css={{
p: "$3 01",
fontSize: "$sm",
height: "calc(100% - 45px)",
}}
>
<Flex column fluid css={{ height: "100%", overflowY: "auto" }}>
<Flex
row
fluid
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
mt: "1px",
pr: "1px",
}}
>
<Text muted css={{ mr: "$3" }}>
Transaction type:{" "}
@@ -207,7 +223,12 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
<Flex
row
fluid
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
pr: "1px",
}}
>
<Text muted css={{ mr: "$3" }}>
Account:{" "}
@@ -229,6 +250,7 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
pr: "1px",
}}
>
<Text muted css={{ mr: "$3" }}>
@@ -242,7 +264,6 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
Amount: { type: "currency", value: e.target.value },
})
}
variant="deep"
css={{ width: "70%", flex: "inherit", height: "$9" }}
/>
</Flex>
@@ -255,6 +276,7 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
pr: "1px",
}}
>
<Text muted css={{ mr: "$3" }}>
@@ -289,6 +311,7 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
pr: "1px",
}}
>
<Text muted css={{ mr: "$3" }}>
@@ -305,7 +328,6 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
: e.target.value,
})
}
variant="deep"
css={{ width: "70%", flex: "inherit", height: "$9" }}
/>
</Flex>

View File

@@ -1,419 +0,0 @@
diff --git a/node_modules/ripple-binary-codec/dist/enums/definitions.json b/node_modules/ripple-binary-codec/dist/enums/definitions.json
index 2333c42..b8f8eab 100644
--- a/node_modules/ripple-binary-codec/dist/enums/definitions.json
+++ b/node_modules/ripple-binary-codec/dist/enums/definitions.json
@@ -1,3 +1,4 @@
+
{
"TYPES": {
"Validation": 10003,
@@ -40,9 +41,7 @@
"Check": 67,
"Nickname": 110,
"Contract": 99,
- "NFTokenPage": 80,
- "NFTokenOffer": 55,
- "NegativeUNL": 78
+ "GeneratorMap": 103
},
"FIELDS": [
[
@@ -95,16 +94,6 @@
"type": "UInt16"
}
],
- [
- "TransferFee",
- {
- "nth": 4,
- "isVLEncoded": false,
- "isSerialized": true,
- "isSigningField": true,
- "type": "UInt16"
- }
- ],
[
"Flags",
{
@@ -455,6 +444,16 @@
"type": "UInt32"
}
],
+ [
+ "EmitGeneration",
+ {
+ "nth": 43,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "UInt32"
+ }
+ ],
[
"IndexNext",
{
@@ -635,16 +634,6 @@
"type": "Hash256"
}
],
- [
- "TokenID",
- {
- "nth": 10,
- "isVLEncoded": false,
- "isSerialized": true,
- "isSigningField": true,
- "type": "Hash256"
- }
- ],
[
"BookDirectory",
{
@@ -916,7 +905,7 @@
}
],
[
- "URI",
+ "Generator",
{
"nth": 5,
"isVLEncoded": true,
@@ -1045,36 +1034,6 @@
"type": "Blob"
}
],
- [
- "UNLModifyValidator",
- {
- "nth": 19,
- "isVLEncoded": true,
- "isSerialized": true,
- "isSigningField": true,
- "type": "Blob"
- }
- ],
- [
- "ValidatorToDisable",
- {
- "nth": 20,
- "isVLEncoded": true,
- "isSerialized": true,
- "isSigningField": true,
- "type": "Blob"
- }
- ],
- [
- "ValidatorToReEnable",
- {
- "nth": 21,
- "isVLEncoded": true,
- "isSerialized": true,
- "isSigningField": true,
- "type": "Blob"
- }
- ],
[
"Account",
{
@@ -1156,7 +1115,7 @@
}
],
[
- "Minter",
+ "EmitCallback",
{
"nth": 9,
"isVLEncoded": true,
@@ -1276,9 +1235,9 @@
}
],
[
- "NonFungibleToken",
+ "Signer",
{
- "nth": 12,
+ "nth": 16,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
@@ -1286,9 +1245,9 @@
}
],
[
- "Signer",
+ "Majority",
{
- "nth": 16,
+ "nth": 18,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
@@ -1296,9 +1255,9 @@
}
],
[
- "Majority",
+ "DisabledValidator",
{
- "nth": 18,
+ "nth": 19,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
@@ -1306,9 +1265,9 @@
}
],
[
- "DisabledValidator",
+ "EmitDetails",
{
- "nth": 19,
+ "nth": 12,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
@@ -1395,16 +1354,6 @@
"type": "STArray"
}
],
- [
- "NonFungibleTokens",
- {
- "nth": 10,
- "isVLEncoded": false,
- "isSerialized": true,
- "isSigningField": true,
- "type": "STArray"
- }
- ],
[
"Majorities",
{
@@ -1415,16 +1364,6 @@
"type": "STArray"
}
],
- [
- "DisabledValidators",
- {
- "nth": 17,
- "isVLEncoded": false,
- "isSerialized": true,
- "isSigningField": true,
- "type": "STArray"
- }
- ],
[
"CloseResolution",
{
@@ -1535,16 +1474,6 @@
"type": "Vector256"
}
],
- [
- "TokenIDs",
- {
- "nth": 4,
- "isVLEncoded": true,
- "isSerialized": true,
- "isSigningField": true,
- "type": "Vector256"
- }
- ],
[
"Transaction",
{
@@ -1596,7 +1525,7 @@
}
],
[
- "TicketCount",
+ "HookStateCount",
{
"nth": 40,
"isVLEncoded": false,
@@ -1606,7 +1535,7 @@
}
],
[
- "TicketSequence",
+ "HookReserveCount",
{
"nth": 41,
"isVLEncoded": false,
@@ -1616,7 +1545,7 @@
}
],
[
- "TokenTaxon",
+ "HookDataMaxSize",
{
"nth": 42,
"isVLEncoded": false,
@@ -1626,23 +1555,23 @@
}
],
[
- "MintedTokens",
+ "HookOn",
{
- "nth": 43,
+ "nth": 16,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
- "type": "UInt32"
+ "type": "UInt64"
}
],
[
- "BurnedTokens",
+ "EmitBurden",
{
- "nth": 44,
+ "nth": 12,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
- "type": "UInt32"
+ "type": "UInt64"
}
],
[
@@ -1686,29 +1615,9 @@
}
],
[
- "PreviousPageMin",
+ "EmitParentTxnID",
{
- "nth": 26,
- "isVLEncoded": false,
- "isSerialized": true,
- "isSigningField": true,
- "type": "Hash256"
- }
- ],
- [
- "NextPageMin",
- {
- "nth": 27,
- "isVLEncoded": false,
- "isSerialized": true,
- "isSigningField": true,
- "type": "Hash256"
- }
- ],
- [
- "BuyOffer",
- {
- "nth": 28,
+ "nth": 10,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
@@ -1716,9 +1625,9 @@
}
],
[
- "SellOffer",
+ "EmitNonce",
{
- "nth": 29,
+ "nth": 11,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
@@ -1735,16 +1644,6 @@
"type": "UInt8"
}
],
- [
- "UNLModifyDisabling",
- {
- "nth": 17,
- "isVLEncoded": false,
- "isSerialized": true,
- "isSigningField": true,
- "type": "UInt8"
- }
- ],
[
"DestinationNode",
{
@@ -1754,36 +1653,6 @@
"isSigningField": true,
"type": "UInt64"
}
- ],
- [
- "Cookie",
- {
- "nth": 10,
- "isVLEncoded": false,
- "isSerialized": true,
- "isSigningField": true,
- "type": "UInt64"
- }
- ],
- [
- "ServerVersion",
- {
- "nth": 11,
- "isVLEncoded": false,
- "isSerialized": true,
- "isSigningField": true,
- "type": "UInt64"
- }
- ],
- [
- "OfferNode",
- {
- "nth": 12,
- "isVLEncoded": false,
- "isSerialized": true,
- "isSigningField": true,
- "type": "UInt64"
- }
]
],
"TRANSACTION_RESULTS": {
@@ -1908,18 +1777,7 @@
"tecDUPLICATE": 149,
"tecKILLED": 150,
"tecHAS_OBLIGATIONS": 151,
- "tecTOO_SOON": 152,
-
- "tecMAX_SEQUENCE_REACHED": 154,
- "tecNO_SUITABLE_PAGE": 155,
- "tecBUY_SELL_MISMATCH": 156,
- "tecOFFER_TYPE_MISMATCH": 157,
- "tecCANT_ACCEPT_OWN_OFFER": 158,
- "tecINSUFFICIENT_FUNDS": 159,
- "tecOBJECT_NOT_FOUND": 160,
- "tecINSUFFICIENT_PAYMENT": 161,
- "tecINCORRECT_ASSET": 162,
- "tecTOO_MANY": 163
+ "tecTOO_SOON": 152
},
"TRANSACTION_TYPES": {
"Invalid": -1,
@@ -1946,13 +1804,11 @@
"DepositPreauth": 19,
"TrustSet": 20,
"AccountDelete": 21,
- "NFTokenMint": 25,
- "NFTokenBurn": 26,
- "NFTokenCreateOffer": 27,
- "NFTokenCancelOffer": 28,
- "NFTokenAcceptOffer": 29,
+ "SetHook": 22,
+ "Invoke": 23,
+ "Batch": 24,
+
"EnableAmendment": 100,
- "SetFee": 101,
- "UNLModify": 102
+ "SetFee": 101
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -29,8 +29,8 @@ export const names = [
*/
export const addFaucetAccount = async (showToast: boolean = false) => {
// Lets limit the number of faucet accounts to 5 for now
if (state.accounts.length > 4) {
return toast.error("You can only have maximum 5 accounts");
if (state.accounts.length > 5) {
return toast.error("You can only have maximum 6 accounts");
}
if (typeof window !== 'undefined') {
@@ -50,8 +50,9 @@ export const addFaucetAccount = async (showToast: boolean = false) => {
if (showToast) {
toast.success("New account created", { id: toastId });
}
const currNames = state.accounts.map(acc => acc.name);
state.accounts.push({
name: names[state.accounts.length],
name: names.filter(name => !currNames.includes(name))[0],
xrp: (json.xrp || 0 * 1000000).toString(),
address: json.address,
secret: json.secret,
@@ -73,4 +74,22 @@ export const addFaucetAccount = async (showToast: boolean = false) => {
}, 10000);
}
}
})();
})();
export const addFunds = async (address: string) => {
const toastId = toast.loading("Requesting funds");
const res = await fetch(`${window.location.origin}/api/faucet?account=${address}`, {
method: "POST",
});
const json: FaucetAccountRes | { error: string } = await res.json();
if ("error" in json) {
return toast.error(json.error, { id: toastId });
} else {
toast.success(`Funds added (${json.xrp} XRP)`, { id: toastId });
const currAccount = state.accounts.find(acc => acc.address === address);
if (currAccount) {
currAccount.xrp = (Number(currAccount.xrp) + (json.xrp * 1000000)).toString();
}
}
}

View File

@@ -1,6 +1,20 @@
import { derive, sign } from "xrpl-accountlib";
import toast from "react-hot-toast";
import state, { IAccount } from "../index";
import calculateHookOn, { TTS } from "../../utils/hookOnCalculator";
import { SetHookData } from "../../components/SetHookDialog";
const hash = async (string: string) => {
const utf8 = new TextEncoder().encode(string);
const hashBuffer = await crypto.subtle.digest('SHA-256', utf8);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray
.map((bytes) => bytes.toString(16).padStart(2, '0'))
.join('');
return hashHex;
}
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
if (!arrayBuffer) {
@@ -30,7 +44,7 @@ function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
* hex string, signs the transaction and deploys it to
* Hooks testnet.
*/
export const deployHook = async (account: IAccount & { name?: string }) => {
export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData) => {
if (
!state.files ||
state.files.length === 0 ||
@@ -45,17 +59,44 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
if (!state.client) {
return;
}
const HookNamespace = await hash(arrayBufferToHex(
state.files?.[state.active]?.compiledContent
).toUpperCase());
const hookOnValues: (keyof TTS)[] = data.Invoke.map(tt => tt.value);
const { HookParameters } = data;
const filteredHookParameters = HookParameters.filter(hp => hp.HookParameter.HookParameterName && hp.HookParameter.HookParameterValue);
// const filteredHookGrants = HookGrants.filter(hg => hg.HookGrant.Authorize || hg.HookGrant.HookHash).map(hg => {
// return {
// HookGrant: {
// ...(hg.HookGrant.Authorize && { Authorize: hg.HookGrant.Authorize }),
// // HookHash: hg.HookGrant.HookHash || undefined
// ...(hg.HookGrant.HookHash && { HookHash: hg.HookGrant.HookHash })
// }
// }
// });
if (typeof window !== "undefined") {
const tx = {
Account: account.address,
TransactionType: "SetHook",
CreateCode: arrayBufferToHex(
state.files?.[state.active]?.compiledContent
).toUpperCase(),
HookOn: "0000000000000000",
Sequence: account.sequence,
Fee: "1000",
Fee: "100000",
Hooks: [
{
Hook: {
CreateCode: arrayBufferToHex(
state.files?.[state.active]?.compiledContent
).toUpperCase(),
HookOn: calculateHookOn(hookOnValues),
HookNamespace,
HookApiVersion: 0,
Flags: 1,
// ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
...(filteredHookParameters.length > 0 && { HookParameters: filteredHookParameters }),
}
}
]
};
const keypair = derive.familySeed(account.secret);
const { signedTransaction } = sign(tx, keypair);
const currentAccount = state.accounts.find(
@@ -64,11 +105,13 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
if (currentAccount) {
currentAccount.isLoading = true;
}
let submitRes;
try {
const submitRes = await state.client.send({
submitRes = await state.client.send({
command: "submit",
tx_blob: signedTransaction,
});
if (submitRes.engine_result === "tesSUCCESS") {
state.deployLogs.push({
type: "success",
@@ -81,7 +124,7 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
} else {
state.deployLogs.push({
type: "error",
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message}`,
message: `[${submitRes.engine_result || submitRes.error}] ${submitRes.engine_result_message || submitRes.error_exception}`,
});
}
} catch (err) {
@@ -94,5 +137,75 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
if (currentAccount) {
currentAccount.isLoading = false;
}
return submitRes;
}
};
export const deleteHook = async (account: IAccount & { name?: string }) => {
if (!state.client) {
return;
}
if (typeof window !== "undefined") {
const tx = {
Account: account.address,
TransactionType: "SetHook",
Sequence: account.sequence,
Fee: "100000",
Hooks: [
{
Hook: {
CreateCode: "",
Flags: 1,
}
}
]
};
const keypair = derive.familySeed(account.secret);
const { signedTransaction } = sign(tx, keypair);
const currentAccount = state.accounts.find(
(acc) => acc.address === account.address
);
if (currentAccount) {
currentAccount.isLoading = true;
}
let submitRes;
const toastId = toast.loading("Deleting hook...");
try {
submitRes = await state.client.send({
command: "submit",
tx_blob: signedTransaction,
});
if (submitRes.engine_result === "tesSUCCESS") {
toast.success('Hook deleted successfully ✅', { id: toastId })
state.deployLogs.push({
type: "success",
message: "Hook deleted successfully ✅",
});
state.deployLogs.push({
type: "success",
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`,
});
} else {
toast.error(`${submitRes.engine_result_message || submitRes.error_exception}`, { id: toastId })
state.deployLogs.push({
type: "error",
message: `[${submitRes.engine_result || submitRes.error}] ${submitRes.engine_result_message || submitRes.error_exception}`,
});
}
} catch (err) {
console.log(err);
toast.error('Error occured while deleting hoook', { id: toastId })
state.deployLogs.push({
type: "error",
message: "Error occured while deleting hook",
});
}
if (currentAccount) {
currentAccount.isLoading = false;
}
return submitRes;
}
};

View File

@@ -1,10 +1,21 @@
// export const templateFileIds = {
// 'starter': '1d14e51e2e02dc0a508cb0733767a914', // TODO currently same as accept
// 'firewall': 'bcd6d0c0fcbe52545ddb802481ff9d26',
// 'notary': 'a789c75f591eeab7932fd702ed8cf9ea',
// 'carbon': '43925143fa19735d8c6505c34d3a6a47',
// 'peggy': 'ceaf352e2a65741341033ab7ef05c448',
// 'headers': '9b448e8a55fab11ef5d1274cb59f9cf3'
// }
export const templateFileIds = {
'starter': '1d14e51e2e02dc0a508cb0733767a914', // TODO currently same as accept
'firewall': 'bcd6d0c0fcbe52545ddb802481ff9d26',
'notary': 'a789c75f591eeab7932fd702ed8cf9ea',
'carbon': '43925143fa19735d8c6505c34d3a6a47',
'peggy': 'ceaf352e2a65741341033ab7ef05c448',
'starter': '1f7d2963d9e342ea092286115274f3e3',
'firewall': '70edec690f0de4dd315fad1f4f996d8c',
'notary': '3d5677768fe8a54c4f6317e185d9ba66',
'carbon': 'a9fbcaf1b816b198c7fc0f62962bebf2',
'doubler': '56b86174aeb70b2b48eee962bad3e355',
'peggy': 'd21298a37e1550b781682014762a567b',
'headers': '9b448e8a55fab11ef5d1274cb59f9cf3'
}
export const apiHeaderFiles = ['hookapi.h', 'sfcodes.h', 'hookmacro.h']

View File

@@ -4,6 +4,11 @@ import { devtools } from 'valtio/utils';
import { XrplClient } from "xrpl-client";
import { SplitSize } from "./actions/persistSplits";
declare module "valtio" {
function useSnapshot<T extends object>(p: T): T;
function snapshot<T extends object>(p: T): T;
}
export interface IFile {
name: string;
language: string;
@@ -122,9 +127,8 @@ const state = proxy<IState>({
accounts: initialAccounts.length > 0 ? initialAccounts : [],
logs: [],
});
// Initialize socket connection
const client = new XrplClient("wss://hooks-testnet.xrpl-labs.com");
const client = new XrplClient(`wss://${process.env.NEXT_PUBLIC_TESTNET_URL}`);
client.on("online", () => {
state.client = ref(client);

View File

@@ -6,7 +6,7 @@ body,
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-y: hidden;
/* overflow-y: hidden; */
}
* {

41
utils/hookOnCalculator.ts Normal file
View File

@@ -0,0 +1,41 @@
export const tts = {
ttPAYMENT: 0,
ttESCROW_CREATE: 1,
ttESCROW_FINISH: 2,
ttACCOUNT_SET: 3,
ttESCROW_CANCEL: 4,
ttREGULAR_KEY_SET: 5,
ttOFFER_CREATE: 7,
ttOFFER_CANCEL: 8,
ttTICKET_CREATE: 10,
ttSIGNER_LIST_SET: 12,
ttPAYCHAN_CREATE: 13,
ttPAYCHAN_FUND: 14,
ttPAYCHAN_CLAIM: 15,
ttCHECK_CREATE: 16,
ttCHECK_CASH: 17,
ttCHECK_CANCEL: 18,
ttDEPOSIT_PREAUTH: 19,
ttTRUST_SET: 20,
ttACCOUNT_DELETE: 21,
ttHOOK_SET: 22
};
export type TTS = typeof tts;
const calculateHookOn = (arr: (keyof TTS)[]) => {
let start = '0x00000000003ff5bf';
arr.forEach(n => {
let v = BigInt(start);
v ^= (BigInt(1) << BigInt(tts[n as keyof TTS]));
let s = v.toString(16);
let l = s.length;
if (l < 16)
s = '0'.repeat(16 - l) + s;
s = '0x' + s;
start = s;
})
return start.substring(2);
}
export default calculateHookOn

2177
yarn.lock

File diff suppressed because it is too large Load Diff