Compare commits
146 Commits
fix/sample
...
feat/add-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
491e10920b | ||
|
|
65bb209713 | ||
|
|
c07e70acc9 | ||
|
|
8fd7f8ecad | ||
|
|
2bb3c646db | ||
|
|
87f10a11b0 | ||
|
|
949fb45ae2 | ||
|
|
ea52f014dd | ||
|
|
77eab8d88d | ||
|
|
4ca8f5f236 | ||
|
|
814b074cc0 | ||
|
|
822f7a30f5 | ||
|
|
1d66137c23 | ||
|
|
4c42e75686 | ||
|
|
501b7fefec | ||
|
|
aa7e1517a2 | ||
|
|
e33093f160 | ||
|
|
923b689c98 | ||
|
|
246e7f137f | ||
|
|
5defd12a11 | ||
|
|
abb7c2bb28 | ||
|
|
12013907f8 | ||
|
|
ec75fff74b | ||
|
|
7c1068449f | ||
|
|
b66d2a09a0 | ||
|
|
54265e024c | ||
|
|
20cb66ba18 | ||
|
|
b7d62dda83 | ||
|
|
c690334f92 | ||
|
|
587f09ec00 | ||
|
|
9296ea1acc | ||
|
|
582fb17c94 | ||
|
|
aff0142870 | ||
|
|
df51d87cb2 | ||
|
|
6a46f5f173 | ||
|
|
9e25cefef9 | ||
|
|
95966fa514 | ||
|
|
f49d69e75d | ||
|
|
da4b2e68ab | ||
|
|
5557b1bcba | ||
|
|
f4b5f98a44 | ||
|
|
b1d39740de | ||
|
|
dfe5589074 | ||
|
|
cdc50da840 | ||
|
|
4893b41936 | ||
|
|
16cbdafb27 | ||
|
|
5559fb7be3 | ||
|
|
3c4305127b | ||
|
|
2a76fa0c35 | ||
|
|
bf21fe36c3 | ||
|
|
a33a3eb6e2 | ||
|
|
919c4e173c | ||
|
|
650324f434 | ||
|
|
74db96e8a5 | ||
|
|
c99c821081 | ||
|
|
e53a533026 | ||
|
|
5f118e00cb | ||
|
|
46e6927c68 | ||
|
|
de95a82c5a | ||
|
|
3b66d64c14 | ||
|
|
6e90a4c3d7 | ||
|
|
2287e7babb | ||
|
|
c219f7ea00 | ||
|
|
e795ce4472 | ||
|
|
04e2274dbf | ||
|
|
6e39b90c1e | ||
|
|
f186a807c1 | ||
|
|
5ad9ed1688 | ||
|
|
842b8a5226 | ||
|
|
234832138f | ||
|
|
28d94a1475 | ||
|
|
594aee6cd2 | ||
|
|
d75910972f | ||
|
|
589c604a12 | ||
|
|
8394a11705 | ||
|
|
4ad329882c | ||
|
|
ee86b91e82 | ||
|
|
d2addf782e | ||
|
|
51f7bd509b | ||
|
|
e064251ff9 | ||
|
|
5aeed7c246 | ||
|
|
8d03edc299 | ||
|
|
95022ef121 | ||
|
|
4519906b78 | ||
|
|
88a47c49a4 | ||
|
|
1ab03f9bed | ||
|
|
84ff667135 | ||
|
|
0d10e782f3 | ||
|
|
84e6763495 | ||
|
|
7ffcfd694d | ||
|
|
77e4917d38 | ||
|
|
e4238a40cc | ||
|
|
42c0b20512 | ||
|
|
43154ff6d8 | ||
|
|
8197b510f9 | ||
|
|
fc7652f48e | ||
|
|
bd32555617 | ||
|
|
fc6f420e1e | ||
|
|
d3c36765de | ||
|
|
2628a12673 | ||
|
|
f6c1869b5d | ||
|
|
62c8b4f217 | ||
|
|
8798e5a233 | ||
|
|
5f7d42843c | ||
|
|
302b36dde8 | ||
|
|
3e7c7b1969 | ||
|
|
936bbc503a | ||
|
|
81890c8833 | ||
|
|
50fa20c39a | ||
|
|
11f2cffc87 | ||
|
|
bbd1d162f0 | ||
|
|
b301a860bf | ||
|
|
ff697b96ea | ||
|
|
48e9898e31 | ||
|
|
2e25242ebe | ||
|
|
e32e07f7fd | ||
|
|
0d2a17008e | ||
|
|
a87b3de6c4 | ||
|
|
23068ff477 | ||
|
|
a12a5dfbac | ||
|
|
5a598cb091 | ||
|
|
be39054a2f | ||
|
|
0add65dd1c | ||
|
|
82170ad4f8 | ||
|
|
af49426eb0 | ||
|
|
48a86e3386 | ||
|
|
c82c35b5a1 | ||
|
|
f849be1f80 | ||
|
|
694d07fa0e | ||
|
|
b9aa3e2adc | ||
|
|
5b573b2379 | ||
|
|
23538b1502 | ||
|
|
723602ebdc | ||
|
|
f8fdeaf9ce | ||
|
|
e75b971718 | ||
|
|
11a35a5932 | ||
|
|
611f875761 | ||
|
|
a7df50c194 | ||
|
|
0c6c60ed29 | ||
|
|
e82662647f | ||
|
|
5490e7205a | ||
|
|
d8e218392a | ||
|
|
723722df58 | ||
|
|
2ff85ede06 | ||
|
|
052a1e5b60 | ||
|
|
09f58f18ae |
@@ -2,4 +2,9 @@ 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_COMPILE_API_BASE_URL="http://localhost:9000"
|
||||
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
|
||||
@@ -87,6 +87,9 @@ By default `@monaco-editor/react` was using 0.29.? version of Monaco editor. @co
|
||||
|
||||
Monaco Languageclient related stuff is found from `./utils/languageClient.ts`. Basically we're connecting the editor to clangd language server which lives on separate backend. That project can be found from https://github.com/eqlabs/xrpl-hooks-compiler/. If you need access to that project ask permissions from @vbar (Vaclav Barta) on GitHub.
|
||||
|
||||
### Language server hover messages
|
||||
If you want to extend hover messages provided by language-server you can add extra docs to `xrpl-hooks-docs/md/` folder. Just make sure the filename is matching with the error code that comes from language server. So lets say you want to add extra documentation for `hooks-func-addr-taken` check create new file called `hooks-func-addr-taken.md` and then remember to import and export it on `docs.ts` file with same logic as the other files.
|
||||
|
||||
## Global state management
|
||||
|
||||
Global state management is handled with library called Valtio (https://github.com/pmndrs/valtio). Initial state can be found from `./state/index.ts` file. All the actions which updates the state is found under `./state/actions/` folder.
|
||||
|
||||
@@ -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 ".";
|
||||
@@ -18,7 +18,8 @@ import {
|
||||
DialogTrigger,
|
||||
} from "./Dialog";
|
||||
import { css } from "../stitches.config";
|
||||
import { Input } from "./Input";
|
||||
import { Input, Label } 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,26 @@ 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
|
||||
disabled={activeAccount.isLoading}
|
||||
css={{ mt: "$3", mr: "$1", ml: "auto" }}
|
||||
onClick={() => {
|
||||
deleteHook(activeAccount);
|
||||
}}
|
||||
>
|
||||
Delete Hook <Trash size="15px" />
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Stack>
|
||||
</DialogDescription>
|
||||
@@ -233,7 +288,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 +308,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 +320,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 +332,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 +394,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 +447,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 +474,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 (
|
||||
@@ -448,7 +491,7 @@ const ImportAccountDialog = () => {
|
||||
<DialogContent>
|
||||
<DialogTitle>Import account</DialogTitle>
|
||||
<DialogDescription>
|
||||
<label>Add account secret</label>
|
||||
<Label>Add account secret</Label>
|
||||
<Input
|
||||
name="secret"
|
||||
type="password"
|
||||
|
||||
@@ -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,12 +142,45 @@ 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: {
|
||||
color: "$textMuted",
|
||||
},
|
||||
},
|
||||
isDisabled: {
|
||||
true: {
|
||||
opacity: 0.6,
|
||||
// pointerEvents: "none",
|
||||
cursor: "auto",
|
||||
"&:hover": {
|
||||
boxShadow: "inherit",
|
||||
},
|
||||
},
|
||||
},
|
||||
outline: {
|
||||
true: {
|
||||
backgroundColor: "transparent",
|
||||
@@ -236,16 +270,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";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { proxy, ref, useSnapshot } from "valtio";
|
||||
import { Select } from ".";
|
||||
import state, { ILog } from "../state";
|
||||
import state, { ILog, transactionsState } from "../state";
|
||||
import { extractJSON } from "../utils/json";
|
||||
import LogBox from "./LogBox";
|
||||
|
||||
@@ -10,14 +10,23 @@ interface ISelect<T = string> {
|
||||
value: T;
|
||||
}
|
||||
|
||||
const streamState = proxy({
|
||||
export interface IStreamState {
|
||||
selectedAccount: ISelect | null;
|
||||
status: "idle" | "opened" | "closed";
|
||||
statusChangeTimestamp?: number;
|
||||
logs: ILog[];
|
||||
socket?: WebSocket;
|
||||
}
|
||||
|
||||
export const streamState = proxy<IStreamState>({
|
||||
selectedAccount: null as ISelect | null,
|
||||
status: "idle",
|
||||
logs: [] as ILog[],
|
||||
socket: undefined as WebSocket | undefined,
|
||||
});
|
||||
|
||||
const DebugStream = () => {
|
||||
const { selectedAccount, logs, socket } = useSnapshot(streamState);
|
||||
const { activeHeader: activeTxTab } = useSnapshot(transactionsState);
|
||||
const { accounts } = useSnapshot(state);
|
||||
|
||||
const accountOptions = accounts.map(acc => ({
|
||||
@@ -39,41 +48,13 @@ const DebugStream = () => {
|
||||
</>
|
||||
);
|
||||
|
||||
const prepareLog = useCallback((str: any): ILog => {
|
||||
if (typeof str !== "string") throw Error("Unrecognized debug log stream!");
|
||||
|
||||
const match = str.match(/([\s\S]+(?:UTC|ISO|GMT[+|-]\d+))\ ?([\s\S]*)/m);
|
||||
const [_, tm, msg] = match || [];
|
||||
|
||||
const extracted = extractJSON(msg);
|
||||
const timestamp = isNaN(Date.parse(tm || ""))
|
||||
? tm
|
||||
: new Date(tm).toLocaleTimeString();
|
||||
|
||||
const message = !extracted
|
||||
? msg
|
||||
: msg.slice(0, extracted.start) + msg.slice(extracted.end + 1);
|
||||
|
||||
const jsonData = extracted
|
||||
? JSON.stringify(extracted.result, null, 2)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
type: "log",
|
||||
message,
|
||||
timestamp,
|
||||
jsonData,
|
||||
defaultCollapsed: true,
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const account = selectedAccount?.value;
|
||||
if (account && (!socket || !socket.url.endsWith(account))) {
|
||||
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) {
|
||||
@@ -82,6 +63,50 @@ const DebugStream = () => {
|
||||
}
|
||||
}, [selectedAccount?.value, socket]);
|
||||
|
||||
const onMount = useCallback(async () => {
|
||||
// deliberately using `proxy` values and not the `useSnapshot` ones to have no dep list
|
||||
const acc = streamState.selectedAccount;
|
||||
const status = streamState.status;
|
||||
|
||||
if (status === "opened" && acc) {
|
||||
// fetch the missing ones
|
||||
try {
|
||||
const url = `https://${process.env.NEXT_PUBLIC_DEBUG_STREAM_URL}/recent/${acc?.value}`;
|
||||
|
||||
// TODO Remove after api sets cors properly
|
||||
const res = await fetch("/api/proxy", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ url }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) return;
|
||||
|
||||
const body = await res.json();
|
||||
|
||||
if (!body?.logs) return;
|
||||
|
||||
const start = streamState.statusChangeTimestamp || 0;
|
||||
streamState.logs = [];
|
||||
pushLog(`Debug stream opened for account ${acc.value}`, {
|
||||
type: "success",
|
||||
});
|
||||
|
||||
const logs = Object.entries(body.logs).filter(([tm]) => +tm >= start);
|
||||
|
||||
logs.forEach(([tm, log]) => pushLog(log));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
onMount();
|
||||
}, [onMount]);
|
||||
|
||||
useEffect(() => {
|
||||
const account = selectedAccount?.value;
|
||||
const socket = streamState.socket;
|
||||
@@ -89,27 +114,27 @@ const DebugStream = () => {
|
||||
|
||||
const onOpen = () => {
|
||||
streamState.logs = [];
|
||||
streamState.logs.push({
|
||||
streamState.status = "opened";
|
||||
streamState.statusChangeTimestamp = Date.now();
|
||||
pushLog(`Debug stream opened for account ${account}`, {
|
||||
type: "success",
|
||||
message: `Debug stream opened for account ${account}`,
|
||||
});
|
||||
};
|
||||
const onError = () => {
|
||||
streamState.logs.push({
|
||||
pushLog("Something went wrong! Check your connection and try again.", {
|
||||
type: "error",
|
||||
message: "Something went wrong! Check your connection and try again.",
|
||||
});
|
||||
};
|
||||
const onClose = (e: CloseEvent) => {
|
||||
streamState.logs.push({
|
||||
pushLog(`Connection was closed. [code: ${e.code}]`, {
|
||||
type: "error",
|
||||
message: `Connection was closed. [code: ${e.code}]`,
|
||||
});
|
||||
streamState.selectedAccount = null;
|
||||
streamState.status = "closed";
|
||||
streamState.statusChangeTimestamp = Date.now();
|
||||
};
|
||||
const onMessage = (event: any) => {
|
||||
if (!event.data) return;
|
||||
streamState.logs.push(prepareLog(event.data));
|
||||
pushLog(event.data);
|
||||
};
|
||||
|
||||
socket.addEventListener("open", onOpen);
|
||||
@@ -123,16 +148,70 @@ const DebugStream = () => {
|
||||
socket.removeEventListener("message", onMessage);
|
||||
socket.removeEventListener("error", onError);
|
||||
};
|
||||
}, [prepareLog, selectedAccount?.value, socket]);
|
||||
}, [selectedAccount?.value, socket]);
|
||||
|
||||
useEffect(() => {
|
||||
const account = transactionsState.transactions.find(
|
||||
tx => tx.header === activeTxTab
|
||||
)?.state.selectedAccount;
|
||||
|
||||
if (account && account.value !== streamState.selectedAccount?.value)
|
||||
streamState.selectedAccount = account;
|
||||
}, [activeTxTab]);
|
||||
|
||||
const clearLog = () => {
|
||||
streamState.logs = [];
|
||||
streamState.statusChangeTimestamp = Date.now();
|
||||
};
|
||||
|
||||
return (
|
||||
<LogBox
|
||||
enhanced
|
||||
renderNav={renderNav}
|
||||
title="Debug stream"
|
||||
logs={logs}
|
||||
clearLog={() => (streamState.logs = [])}
|
||||
clearLog={clearLog}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebugStream;
|
||||
|
||||
export const pushLog = (
|
||||
str: any,
|
||||
opts: Partial<Pick<ILog, "type">> = {}
|
||||
): ILog | undefined => {
|
||||
if (!str) return;
|
||||
if (typeof str !== "string") throw Error("Unrecognized debug log stream!");
|
||||
|
||||
const match = str.match(/([\s\S]+(?:UTC|ISO|GMT[+|-]\d+))?\ ?([\s\S]*)/m);
|
||||
const [_, tm, msg] = match || [];
|
||||
|
||||
const timestamp = Date.parse(tm || "") || undefined;
|
||||
const timestring = !timestamp ? tm : new Date(timestamp).toLocaleTimeString();
|
||||
|
||||
const extracted = extractJSON(msg);
|
||||
const message = !extracted
|
||||
? msg
|
||||
: msg.slice(0, extracted.start) + msg.slice(extracted.end + 1);
|
||||
|
||||
const jsonData = extracted
|
||||
? JSON.stringify(extracted.result, null, 2)
|
||||
: undefined;
|
||||
|
||||
if (extracted?.result?.id?._Request?.includes("hooks-builder-req")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { type = "log" } = opts;
|
||||
const log: ILog = {
|
||||
type,
|
||||
message,
|
||||
timestring,
|
||||
jsonData,
|
||||
defaultCollapsed: true,
|
||||
};
|
||||
|
||||
if (log) streamState.logs.push(log);
|
||||
return log;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import NewWindow from "react-new-window";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import { useSnapshot } from "valtio";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import {
|
||||
createNewFile,
|
||||
@@ -46,9 +47,9 @@ import {
|
||||
} from "./Dialog";
|
||||
import Flex from "./Flex";
|
||||
import Stack from "./Stack";
|
||||
import Input from "./Input";
|
||||
import { Input, Label } from "./Input";
|
||||
import Text from "./Text";
|
||||
import toast from "react-hot-toast";
|
||||
import Tooltip from "./Tooltip";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogContent,
|
||||
@@ -59,8 +60,6 @@ import {
|
||||
} from "./AlertDialog";
|
||||
import { styled } from "../stitches.config";
|
||||
|
||||
const DEFAULT_EXTENSION = ".c";
|
||||
|
||||
const ErrorText = styled(Text, {
|
||||
color: "$error",
|
||||
mt: "$1",
|
||||
@@ -91,33 +90,38 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
const validateFilename = useCallback(
|
||||
(filename: string): { error: string | null } => {
|
||||
// check if filename already exists
|
||||
if (snap.files.find(file => file.name === filename)) {
|
||||
if (!filename) {
|
||||
return { error: "You need to add filename" };
|
||||
}
|
||||
if (snap.files.find((file) => file.name === filename)) {
|
||||
return { error: "Filename already exists." };
|
||||
}
|
||||
|
||||
// check for illegal characters
|
||||
const ILLEGAL_REGEX = /[/]/gi;
|
||||
if (filename.match(ILLEGAL_REGEX)) {
|
||||
return { error: "Filename contains illegal characters" };
|
||||
if (!filename.includes(".") || filename[filename.length - 1] === ".") {
|
||||
return { error: "Filename should include file extension" };
|
||||
}
|
||||
|
||||
// check for illegal characters
|
||||
const ALPHA_NUMERICAL_REGEX = /^[A-Za-z0-9_-]+[.][A-Za-z0-9]{1,4}$/g;
|
||||
if (!filename.match(ALPHA_NUMERICAL_REGEX)) {
|
||||
return {
|
||||
error: `Filename can contain only characters from a-z, A-Z, 0-9, "_" and "-" and it needs to have file extension (e.g. ".c")`,
|
||||
};
|
||||
}
|
||||
// More checks in future
|
||||
return { error: null };
|
||||
},
|
||||
[snap.files]
|
||||
);
|
||||
const handleConfirm = useCallback(() => {
|
||||
// add default extension in case omitted
|
||||
let _filename = filename.includes(".")
|
||||
? filename
|
||||
: filename + DEFAULT_EXTENSION;
|
||||
const chk = validateFilename(_filename);
|
||||
if (chk.error) {
|
||||
const chk = validateFilename(filename);
|
||||
if (chk && chk.error) {
|
||||
setNewfileError(`Error: ${chk.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsNewfileDialogOpen(false);
|
||||
createNewFile(_filename);
|
||||
createNewFile(filename);
|
||||
setFilename("");
|
||||
}, [filename, setIsNewfileDialogOpen, setFilename, validateFilename]);
|
||||
|
||||
@@ -218,7 +222,7 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
<DialogContent>
|
||||
<DialogTitle>Create new file</DialogTitle>
|
||||
<DialogDescription>
|
||||
<label>Filename</label>
|
||||
<Label>Filename</Label>
|
||||
<Input
|
||||
value={filename}
|
||||
onChange={(e) => setFilename(e.target.value)}
|
||||
@@ -364,44 +368,65 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
isLoading={snap.zipLoading}
|
||||
onClick={downloadAsZip}
|
||||
outline
|
||||
size="sm"
|
||||
css={{ alignItems: "center" }}
|
||||
<Tooltip content="Download as ZIP">
|
||||
<Button
|
||||
isLoading={snap.zipLoading}
|
||||
onClick={downloadAsZip}
|
||||
outline
|
||||
size="sm"
|
||||
css={{ alignItems: "center" }}
|
||||
>
|
||||
<DownloadSimple size="16px" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip content="Copy share link to clipboard">
|
||||
<Button
|
||||
outline
|
||||
size="sm"
|
||||
css={{ alignItems: "center" }}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`${window.location.origin}/develop/${snap.gistId}`
|
||||
);
|
||||
toast.success("Copied share link to clipboard!");
|
||||
}}
|
||||
>
|
||||
<Share size="16px" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={
|
||||
session && session.user
|
||||
? snap.gistOwner === session?.user.username
|
||||
? "Sync to Gist"
|
||||
: "Create as a new Gist"
|
||||
: "You need to be logged in to sync with Gist"
|
||||
}
|
||||
>
|
||||
<DownloadSimple size="16px" />
|
||||
</Button>
|
||||
<Button
|
||||
outline
|
||||
size="sm"
|
||||
css={{ alignItems: "center" }}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`${window.location.origin}/develop/${snap.gistId}`
|
||||
);
|
||||
toast.success("Copied share link to clipboard!");
|
||||
}}
|
||||
>
|
||||
<Share size="16px" />
|
||||
</Button>
|
||||
<Button
|
||||
outline
|
||||
size="sm"
|
||||
disabled={!session || !session.user}
|
||||
isLoading={snap.gistLoading}
|
||||
css={{ alignItems: "center" }}
|
||||
onClick={() => {
|
||||
if (snap.gistOwner === session?.user.username) {
|
||||
syncToGist(session);
|
||||
} else {
|
||||
setCreateNewAlertOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CloudArrowUp size="16px" />
|
||||
</Button>
|
||||
<Button
|
||||
outline
|
||||
size="sm"
|
||||
isDisabled={!session || !session.user}
|
||||
isLoading={snap.gistLoading}
|
||||
css={{ alignItems: "center" }}
|
||||
onClick={() => {
|
||||
if (!session || !session.user) {
|
||||
return;
|
||||
}
|
||||
if (snap.gistOwner === session?.user.username) {
|
||||
syncToGist(session);
|
||||
} else {
|
||||
setCreateNewAlertOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{snap.gistOwner === session?.user.username ? (
|
||||
<CloudArrowUp size="16px" />
|
||||
) : (
|
||||
<FilePlus size="16px" />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -499,7 +524,7 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
<DialogContent>
|
||||
<DialogTitle>Editor settings</DialogTitle>
|
||||
<DialogDescription>
|
||||
<label>Tab size</label>
|
||||
<Label>Tab size</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
|
||||
@@ -5,7 +5,6 @@ import type monaco from "monaco-editor";
|
||||
import { ArrowBendLeftUp } from "phosphor-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useRouter } from "next/router";
|
||||
import uniqBy from "lodash.uniqby";
|
||||
|
||||
import Box from "./Box";
|
||||
import Container from "./Container";
|
||||
@@ -45,18 +44,15 @@ const setMarkers = (monacoE: typeof monaco) => {
|
||||
// Get all the markers that are active at the moment,
|
||||
// Also if same error is there twice, we can show the content
|
||||
// only once (that's why we're using uniqBy)
|
||||
const markers = uniqBy(
|
||||
monacoE.editor
|
||||
.getModelMarkers({})
|
||||
// Filter out the markers that are hooks specific
|
||||
.filter(
|
||||
(marker) =>
|
||||
typeof marker?.code === "string" &&
|
||||
// Take only markers that starts with "hooks-"
|
||||
marker?.code?.includes("hooks-")
|
||||
),
|
||||
"code"
|
||||
);
|
||||
const markers = monacoE.editor
|
||||
.getModelMarkers({})
|
||||
// Filter out the markers that are hooks specific
|
||||
.filter(
|
||||
(marker) =>
|
||||
typeof marker?.code === "string" &&
|
||||
// Take only markers that starts with "hooks-"
|
||||
marker?.code?.includes("hooks-")
|
||||
);
|
||||
|
||||
// Get the active model (aka active file you're editing)
|
||||
// const model = monacoE.editor?.getModel(
|
||||
@@ -226,6 +222,22 @@ const HooksEditor = () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Hacky way to hide Peek menu
|
||||
editor.onContextMenu((e) => {
|
||||
const host =
|
||||
document.querySelector<HTMLElement>(".shadow-root-host");
|
||||
|
||||
const contextMenuItems =
|
||||
host?.shadowRoot?.querySelectorAll("li.action-item");
|
||||
contextMenuItems?.forEach((k) => {
|
||||
// If menu item contains "Peek" lets hide it
|
||||
if (k.querySelector(".action-label")?.textContent === "Peek") {
|
||||
// @ts-expect-error
|
||||
k["style"].display = "none";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
validateWritability(editor);
|
||||
}}
|
||||
theme={theme === "dark" ? "dark" : "light"}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import React from "react";
|
||||
import { styled } from "../stitches.config";
|
||||
import * as LabelPrim from '@radix-ui/react-label';
|
||||
|
||||
export const Input = styled("input", {
|
||||
// Reset
|
||||
@@ -58,6 +60,8 @@ export const Input = styled("input", {
|
||||
},
|
||||
"&:read-only": {
|
||||
backgroundColor: "$mauve2",
|
||||
color: "$text",
|
||||
opacity: 0.8,
|
||||
"&:focus": {
|
||||
boxShadow: "inset 0px 0px 0px 1px $colors$mauve7",
|
||||
},
|
||||
@@ -148,4 +152,18 @@ 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;
|
||||
|
||||
|
||||
const LabelRoot = (props: LabelPrim.LabelProps) => <LabelPrim.Root {...props} />
|
||||
|
||||
export const Label = styled(LabelRoot, {
|
||||
display: 'inline-block',
|
||||
mb: '$1'
|
||||
})
|
||||
@@ -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);
|
||||
|
||||
@@ -133,7 +147,7 @@ const LogBox: FC<ILogBox> = ({ title, clearLog, logs, children, renderNav, enhan
|
||||
|
||||
export const Log: FC<ILog> = ({
|
||||
type,
|
||||
timestamp: timestamp,
|
||||
timestring,
|
||||
message: _message,
|
||||
link,
|
||||
linkText,
|
||||
@@ -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}
|
||||
@@ -172,8 +186,17 @@ export const Log: FC<ILog> = ({
|
||||
},
|
||||
[accounts]
|
||||
);
|
||||
_message = _message.trim().replace(/\n /gi, "\n");
|
||||
const message = enrichAccounts(_message);
|
||||
|
||||
let message: ReactNode;
|
||||
|
||||
if (typeof _message === 'string') {
|
||||
_message = _message.trim().replace(/\n /gi, "\n");
|
||||
message = enrichAccounts(_message)
|
||||
}
|
||||
else {
|
||||
message = _message
|
||||
}
|
||||
|
||||
const jsonData = enrichAccounts(_jsonData);
|
||||
|
||||
return (
|
||||
@@ -183,7 +206,11 @@ export const Log: FC<ILog> = ({
|
||||
activeAccountAddress={dialogAccount}
|
||||
/>
|
||||
<LogText variant={type}>
|
||||
{timestamp && <Text muted monospace>{timestamp} </Text>}
|
||||
{timestring && (
|
||||
<Text muted monospace>
|
||||
{timestring}{" "}
|
||||
</Text>
|
||||
)}
|
||||
<Pre>{message} </Pre>
|
||||
{link && (
|
||||
<NextLink href={link} shallow passHref>
|
||||
@@ -197,6 +224,7 @@ export const Log: FC<ILog> = ({
|
||||
)}
|
||||
{expanded && jsonData && <Pre block>{jsonData}</Pre>}
|
||||
</LogText>
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -128,7 +128,7 @@ const Navigation = () => {
|
||||
</DialogTrigger>
|
||||
<DialogContent
|
||||
css={{
|
||||
maxWidth: "100%",
|
||||
maxWidth: "1080px",
|
||||
width: "80vw",
|
||||
height: "80%",
|
||||
backgroundColor: "$mauve1 !important",
|
||||
@@ -228,7 +228,7 @@ const Navigation = () => {
|
||||
as="a"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
href="https://xrpl-hooks.readme.io/docs"
|
||||
href="https://xrpl-hooks.readme.io/v2.0/docs"
|
||||
>
|
||||
<ArrowUpRight size="15px" /> Hooks documentation
|
||||
</Text>
|
||||
@@ -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
|
||||
@@ -399,7 +400,7 @@ const Navigation = () => {
|
||||
</Button>
|
||||
</Link>
|
||||
</ButtonGroup>
|
||||
<Link href="https://xrpl-hooks.readme.io/" passHref>
|
||||
<Link href="https://xrpl-hooks.readme.io/v2.0" passHref>
|
||||
<a target="_blank" rel="noreferrer noopener">
|
||||
<Button outline>
|
||||
<BookOpen size="15px" />
|
||||
|
||||
109
components/Popover.tsx
Normal file
109
components/Popover.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
||||
import { styled, keyframes } from "../stitches.config";
|
||||
|
||||
const slideUpAndFade = keyframes({
|
||||
"0%": { opacity: 0, transform: "translateY(2px)" },
|
||||
"100%": { opacity: 1, transform: "translateY(0)" },
|
||||
});
|
||||
|
||||
const slideRightAndFade = keyframes({
|
||||
"0%": { opacity: 0, transform: "translateX(-2px)" },
|
||||
"100%": { opacity: 1, transform: "translateX(0)" },
|
||||
});
|
||||
|
||||
const slideDownAndFade = keyframes({
|
||||
"0%": { opacity: 0, transform: "translateY(-2px)" },
|
||||
"100%": { opacity: 1, transform: "translateY(0)" },
|
||||
});
|
||||
|
||||
const slideLeftAndFade = keyframes({
|
||||
"0%": { opacity: 0, transform: "translateX(2px)" },
|
||||
"100%": { opacity: 1, transform: "translateX(0)" },
|
||||
});
|
||||
const StyledContent = styled(PopoverPrimitive.Content, {
|
||||
borderRadius: 4,
|
||||
padding: "$3 $3",
|
||||
fontSize: 12,
|
||||
lineHeight: 1,
|
||||
color: "$text",
|
||||
backgroundColor: "$background",
|
||||
boxShadow:
|
||||
"0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)",
|
||||
"@media (prefers-reduced-motion: no-preference)": {
|
||||
animationDuration: "400ms",
|
||||
animationTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)",
|
||||
willChange: "transform, opacity",
|
||||
'&[data-state="open"]': {
|
||||
'&[data-side="top"]': { animationName: slideDownAndFade },
|
||||
'&[data-side="right"]': { animationName: slideLeftAndFade },
|
||||
'&[data-side="bottom"]': { animationName: slideUpAndFade },
|
||||
'&[data-side="left"]': { animationName: slideRightAndFade },
|
||||
},
|
||||
},
|
||||
".dark &": {
|
||||
backgroundColor: "$mauve5",
|
||||
boxShadow:
|
||||
"0px 5px 38px -2px rgba(22, 23, 24, 1), 0px 10px 20px 0px rgba(22, 23, 24, 1)",
|
||||
},
|
||||
});
|
||||
|
||||
const StyledArrow = styled(PopoverPrimitive.Arrow, {
|
||||
fill: "$colors$mauve2",
|
||||
".dark &": {
|
||||
fill: "$mauve5",
|
||||
},
|
||||
});
|
||||
|
||||
const StyledClose = styled(PopoverPrimitive.Close, {
|
||||
all: "unset",
|
||||
fontFamily: "inherit",
|
||||
borderRadius: "100%",
|
||||
height: 25,
|
||||
width: 25,
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: "$text",
|
||||
position: "absolute",
|
||||
top: 5,
|
||||
right: 5,
|
||||
});
|
||||
|
||||
// Exports
|
||||
export const PopoverRoot = PopoverPrimitive.Root;
|
||||
export const PopoverTrigger = PopoverPrimitive.Trigger;
|
||||
export const PopoverContent = StyledContent;
|
||||
export const PopoverArrow = StyledArrow;
|
||||
export const PopoverClose = StyledClose;
|
||||
|
||||
interface IPopover {
|
||||
content: string | ReactNode;
|
||||
open?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const Popover: React.FC<
|
||||
IPopover & React.ComponentProps<typeof PopoverContent>
|
||||
> = ({
|
||||
children,
|
||||
content,
|
||||
open,
|
||||
defaultOpen = false,
|
||||
onOpenChange,
|
||||
...rest
|
||||
}) => (
|
||||
<PopoverRoot
|
||||
open={open}
|
||||
defaultOpen={defaultOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
>
|
||||
<PopoverTrigger asChild>{children}</PopoverTrigger>
|
||||
<PopoverContent sideOffset={5} {...rest}>
|
||||
{content} <PopoverArrow offset={5} className="arrow" />
|
||||
</PopoverContent>
|
||||
</PopoverRoot>
|
||||
);
|
||||
|
||||
export default Popover;
|
||||
@@ -1,56 +1,127 @@
|
||||
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,
|
||||
minHeight: 0,
|
||||
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,
|
||||
},
|
||||
};
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default styled(Select, {});
|
||||
|
||||
306
components/SetHookDialog.tsx
Normal file
306
components/SetHookDialog.tsx
Normal file
@@ -0,0 +1,306 @@
|
||||
import React, { useCallback, useEffect, 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, Label } 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";
|
||||
import { sha256 } from "../state/actions/deployHook";
|
||||
|
||||
const transactionOptions = Object.keys(tts).map((key) => ({
|
||||
label: key,
|
||||
value: key as keyof TTS,
|
||||
}));
|
||||
|
||||
export type SetHookData = {
|
||||
Invoke: {
|
||||
value: keyof TTS;
|
||||
label: string;
|
||||
}[];
|
||||
HookNamespace: 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,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<SetHookData>({
|
||||
defaultValues: {
|
||||
HookNamespace: snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || "",
|
||||
},
|
||||
});
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control,
|
||||
name: "HookParameters", // unique name for your Field Array
|
||||
});
|
||||
|
||||
// Update value if activeWat changes
|
||||
useEffect(() => {
|
||||
setValue(
|
||||
"HookNamespace",
|
||||
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || ""
|
||||
);
|
||||
}, [snap.activeWat, snap.files, setValue]);
|
||||
// const {
|
||||
// fields: grantFields,
|
||||
// append: grantAppend,
|
||||
// remove: grantRemove,
|
||||
// } = useFieldArray({
|
||||
// control,
|
||||
// name: "HookGrants", // unique name for your Field Array
|
||||
// });
|
||||
const [hashedNamespace, setHashedNamespace] = useState("");
|
||||
const namespace = watch(
|
||||
"HookNamespace",
|
||||
snap.files?.[snap.active]?.name?.split(".")?.[0] || ""
|
||||
);
|
||||
const calculateHashedValue = useCallback(async () => {
|
||||
const hashedVal = await sha256(namespace);
|
||||
setHashedNamespace(hashedVal.toUpperCase());
|
||||
}, [namespace]);
|
||||
useEffect(() => {
|
||||
calculateHashedValue();
|
||||
}, [namespace, calculateHashedValue]);
|
||||
|
||||
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>Hook Namespace Seed</Label>
|
||||
<Input
|
||||
{...register("HookNamespace", { required: true })}
|
||||
autoComplete={"off"}
|
||||
defaultValue={
|
||||
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || ""
|
||||
}
|
||||
/>
|
||||
{errors.HookNamespace?.type === "required" && (
|
||||
<Box css={{ display: "inline", color: "$red11" }}>
|
||||
Namespace is required
|
||||
</Box>
|
||||
)}
|
||||
<Box css={{ mt: "$3" }}>
|
||||
<Label>Hook Namespace (sha256)</Label>
|
||||
<Input readOnly value={hashedNamespace} />
|
||||
</Box>
|
||||
</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="Value (hex-quoted)"
|
||||
{...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;
|
||||
32
components/Switch.tsx
Normal file
32
components/Switch.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { styled } from "../stitches.config";
|
||||
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
||||
|
||||
const StyledSwitch = styled(SwitchPrimitive.Root, {
|
||||
all: "unset",
|
||||
width: 42,
|
||||
height: 25,
|
||||
backgroundColor: "$mauve9",
|
||||
borderRadius: "9999px",
|
||||
position: "relative",
|
||||
boxShadow: `0 2px 10px $colors$mauve2`,
|
||||
WebkitTapHighlightColor: "rgba(0, 0, 0, 0)",
|
||||
"&:focus": { boxShadow: `0 0 0 2px $colors$mauveA2` },
|
||||
'&[data-state="checked"]': { backgroundColor: "$green11" },
|
||||
});
|
||||
|
||||
const StyledThumb = styled(SwitchPrimitive.Thumb, {
|
||||
display: "block",
|
||||
width: 21,
|
||||
height: 21,
|
||||
backgroundColor: "white",
|
||||
borderRadius: "9999px",
|
||||
boxShadow: `0 2px 2px $colors$mauveA6`,
|
||||
transition: "transform 100ms",
|
||||
transform: "translateX(2px)",
|
||||
willChange: "transform",
|
||||
'&[data-state="checked"]': { transform: "translateX(19px)" },
|
||||
});
|
||||
|
||||
// Exports
|
||||
export const Switch = StyledSwitch;
|
||||
export const SwitchThumb = StyledThumb;
|
||||
@@ -6,7 +6,7 @@ import React, {
|
||||
useCallback,
|
||||
} from "react";
|
||||
import type { ReactNode, ReactElement } from "react";
|
||||
import { Box, Button, Flex, Input, Stack, Text } from ".";
|
||||
import { Box, Button, Flex, Input, Label, Stack, Text } from ".";
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
@@ -29,7 +29,7 @@ interface TabProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// TODO customise strings shown
|
||||
// TODO customise messages shown
|
||||
interface Props {
|
||||
activeIndex?: number;
|
||||
activeHeader?: string;
|
||||
@@ -40,6 +40,7 @@ interface Props {
|
||||
forceDefaultExtension?: boolean;
|
||||
onCreateNewTab?: (name: string) => any;
|
||||
onCloseTab?: (index: number, header?: string) => any;
|
||||
onChangeActive?: (index: number, header?: string) => any;
|
||||
}
|
||||
|
||||
export const Tab = (props: TabProps) => null;
|
||||
@@ -52,11 +53,12 @@ export const Tabs = ({
|
||||
keepAllAlive = false,
|
||||
onCreateNewTab,
|
||||
onCloseTab,
|
||||
onChangeActive,
|
||||
defaultExtension = "",
|
||||
forceDefaultExtension,
|
||||
}: Props) => {
|
||||
const [active, setActive] = useState(activeIndex || 0);
|
||||
const tabs: TabProps[] = children.map((elem) => elem.props);
|
||||
const tabs: TabProps[] = children.map(elem => elem.props);
|
||||
|
||||
const [isNewtabDialogOpen, setIsNewtabDialogOpen] = useState(false);
|
||||
const [tabname, setTabname] = useState("");
|
||||
@@ -68,8 +70,9 @@ export const Tabs = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (activeHeader) {
|
||||
const idx = tabs.findIndex((tab) => tab.header === activeHeader);
|
||||
setActive(idx);
|
||||
const idx = tabs.findIndex(tab => tab.header === activeHeader);
|
||||
if (idx !== -1) setActive(idx);
|
||||
else setActive(0);
|
||||
}
|
||||
}, [activeHeader, tabs]);
|
||||
|
||||
@@ -80,7 +83,7 @@ export const Tabs = ({
|
||||
|
||||
const validateTabname = useCallback(
|
||||
(tabname: string): { error: string | null } => {
|
||||
if (tabs.find((tab) => tab.header === tabname)) {
|
||||
if (tabs.find(tab => tab.header === tabname)) {
|
||||
return { error: "Name already exists." };
|
||||
}
|
||||
return { error: null };
|
||||
@@ -88,6 +91,14 @@ export const Tabs = ({
|
||||
[tabs]
|
||||
);
|
||||
|
||||
const handleActiveChange = useCallback(
|
||||
(idx: number, header?: string) => {
|
||||
setActive(idx);
|
||||
onChangeActive?.(idx, header);
|
||||
},
|
||||
[onChangeActive]
|
||||
);
|
||||
|
||||
const handleCreateTab = useCallback(() => {
|
||||
// add default extension in case omitted
|
||||
let _tabname = tabname.includes(".") ? tabname : tabname + defaultExtension;
|
||||
@@ -103,11 +114,20 @@ export const Tabs = ({
|
||||
|
||||
setIsNewtabDialogOpen(false);
|
||||
setTabname("");
|
||||
// switch to new tab?
|
||||
setActive(tabs.length);
|
||||
|
||||
onCreateNewTab?.(_tabname);
|
||||
}, [tabname, defaultExtension, validateTabname, onCreateNewTab, tabs.length]);
|
||||
|
||||
// switch to new tab?
|
||||
handleActiveChange(tabs.length, _tabname);
|
||||
}, [
|
||||
tabname,
|
||||
defaultExtension,
|
||||
forceDefaultExtension,
|
||||
validateTabname,
|
||||
onCreateNewTab,
|
||||
handleActiveChange,
|
||||
tabs.length,
|
||||
]);
|
||||
|
||||
const handleCloseTab = useCallback(
|
||||
(idx: number) => {
|
||||
@@ -128,7 +148,7 @@ export const Tabs = ({
|
||||
gap: "$3",
|
||||
flex: 1,
|
||||
flexWrap: "nowrap",
|
||||
marginBottom: "-1px",
|
||||
marginBottom: "$2",
|
||||
width: "100%",
|
||||
overflow: "auto",
|
||||
}}
|
||||
@@ -138,8 +158,8 @@ export const Tabs = ({
|
||||
key={tab.header}
|
||||
role="tab"
|
||||
tabIndex={idx}
|
||||
onClick={() => setActive(idx)}
|
||||
onKeyPress={() => setActive(idx)}
|
||||
onClick={() => handleActiveChange(idx, tab.header)}
|
||||
onKeyPress={() => handleActiveChange(idx, tab.header)}
|
||||
outline={active !== idx}
|
||||
size="sm"
|
||||
css={{
|
||||
@@ -192,11 +212,11 @@ export const Tabs = ({
|
||||
<DialogContent>
|
||||
<DialogTitle>Create new tab</DialogTitle>
|
||||
<DialogDescription>
|
||||
<label>Tabname</label>
|
||||
<Label>Tabname</Label>
|
||||
<Input
|
||||
value={tabname}
|
||||
onChange={(e) => setTabname(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
onChange={e => setTabname(e.target.value)}
|
||||
onKeyPress={e => {
|
||||
if (e.key === "Enter") {
|
||||
handleCreateTab();
|
||||
}
|
||||
|
||||
92
components/Tooltip.tsx
Normal file
92
components/Tooltip.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import React from "react";
|
||||
import { styled, keyframes } from "../stitches.config";
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
|
||||
const slideUpAndFade = keyframes({
|
||||
"0%": { opacity: 0, transform: "translateY(2px)" },
|
||||
"100%": { opacity: 1, transform: "translateY(0)" },
|
||||
});
|
||||
|
||||
const slideRightAndFade = keyframes({
|
||||
"0%": { opacity: 0, transform: "translateX(-2px)" },
|
||||
"100%": { opacity: 1, transform: "translateX(0)" },
|
||||
});
|
||||
|
||||
const slideDownAndFade = keyframes({
|
||||
"0%": { opacity: 0, transform: "translateY(-2px)" },
|
||||
"100%": { opacity: 1, transform: "translateY(0)" },
|
||||
});
|
||||
|
||||
const slideLeftAndFade = keyframes({
|
||||
"0%": { opacity: 0, transform: "translateX(2px)" },
|
||||
"100%": { opacity: 1, transform: "translateX(0)" },
|
||||
});
|
||||
|
||||
const StyledContent = styled(TooltipPrimitive.Content, {
|
||||
borderRadius: 4,
|
||||
padding: "$2 $3",
|
||||
fontSize: 12,
|
||||
lineHeight: 1,
|
||||
color: "$text",
|
||||
backgroundColor: "$background",
|
||||
boxShadow:
|
||||
"hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px",
|
||||
"@media (prefers-reduced-motion: no-preference)": {
|
||||
animationDuration: "400ms",
|
||||
animationTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)",
|
||||
animationFillMode: "forwards",
|
||||
willChange: "transform, opacity",
|
||||
'&[data-state="delayed-open"]': {
|
||||
'&[data-side="top"]': { animationName: slideDownAndFade },
|
||||
'&[data-side="right"]': { animationName: slideLeftAndFade },
|
||||
'&[data-side="bottom"]': { animationName: slideUpAndFade },
|
||||
'&[data-side="left"]': { animationName: slideRightAndFade },
|
||||
},
|
||||
},
|
||||
".dark &": {
|
||||
boxShadow:
|
||||
"0px 0px 10px 2px rgba(0,0,0,.45), hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px",
|
||||
},
|
||||
".light &": {
|
||||
boxShadow:
|
||||
"0px 0px 10px 2px rgba(0,0,0,.25), hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px",
|
||||
},
|
||||
});
|
||||
|
||||
const StyledArrow = styled(TooltipPrimitive.Arrow, {
|
||||
fill: "$background",
|
||||
});
|
||||
|
||||
interface ITooltip {
|
||||
content: string;
|
||||
open?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const Tooltip: React.FC<
|
||||
React.ComponentProps<typeof StyledContent> & ITooltip
|
||||
> = ({
|
||||
children,
|
||||
content,
|
||||
open,
|
||||
defaultOpen = false,
|
||||
onOpenChange,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<TooltipPrimitive.Root
|
||||
open={open}
|
||||
defaultOpen={defaultOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
>
|
||||
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
|
||||
<StyledContent side="bottom" align="center" {...rest}>
|
||||
<div dangerouslySetInnerHTML={{ __html: content }} />
|
||||
<StyledArrow offset={5} width={11} height={5} />
|
||||
</StyledContent>
|
||||
</TooltipPrimitive.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tooltip;
|
||||
377
components/Transaction.tsx
Normal file
377
components/Transaction.tsx
Normal file
@@ -0,0 +1,377 @@
|
||||
import { Play } from "phosphor-react";
|
||||
import { FC, useCallback, useEffect } from "react";
|
||||
import { useSnapshot } from "valtio";
|
||||
import transactionsData from "../content/transactions.json";
|
||||
import state, { modifyTransaction } from "../state";
|
||||
import { sendTransaction } from "../state/actions";
|
||||
import Box from "./Box";
|
||||
import Button from "./Button";
|
||||
import Container from "./Container";
|
||||
import { streamState } from "./DebugStream";
|
||||
import Flex from "./Flex";
|
||||
import Input from "./Input";
|
||||
import Text from "./Text";
|
||||
import Select from "./Select";
|
||||
|
||||
type TxFields = Omit<
|
||||
typeof transactionsData[0],
|
||||
"Account" | "Sequence" | "TransactionType"
|
||||
>;
|
||||
|
||||
type OtherFields = (keyof Omit<TxFields, "Destination">)[];
|
||||
|
||||
type SelectOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export interface TransactionState {
|
||||
selectedTransaction: SelectOption | null;
|
||||
selectedAccount: SelectOption | null;
|
||||
selectedDestAccount: SelectOption | null;
|
||||
txIsLoading: boolean;
|
||||
txIsDisabled: boolean;
|
||||
txFields: TxFields;
|
||||
}
|
||||
|
||||
export interface TransactionProps {
|
||||
header: string;
|
||||
state: TransactionState;
|
||||
}
|
||||
|
||||
const Transaction: FC<TransactionProps> = ({
|
||||
header,
|
||||
state: {
|
||||
selectedAccount,
|
||||
selectedDestAccount,
|
||||
selectedTransaction,
|
||||
txFields,
|
||||
txIsDisabled,
|
||||
txIsLoading,
|
||||
},
|
||||
...props
|
||||
}) => {
|
||||
const { accounts } = useSnapshot(state);
|
||||
|
||||
const setState = useCallback(
|
||||
(pTx?: Partial<TransactionState>) => {
|
||||
modifyTransaction(header, pTx);
|
||||
},
|
||||
[header]
|
||||
);
|
||||
|
||||
const transactionsOptions = transactionsData.map(tx => ({
|
||||
value: tx.TransactionType,
|
||||
label: tx.TransactionType,
|
||||
}));
|
||||
|
||||
const accountOptions: SelectOption[] = accounts.map(acc => ({
|
||||
label: acc.name,
|
||||
value: acc.address,
|
||||
}));
|
||||
|
||||
const destAccountOptions: SelectOption[] = accounts
|
||||
.map(acc => ({
|
||||
label: acc.name,
|
||||
value: acc.address,
|
||||
}))
|
||||
.filter(acc => acc.value !== selectedAccount?.value);
|
||||
|
||||
useEffect(() => {
|
||||
const transactionType = selectedTransaction?.value;
|
||||
const account = accounts.find(
|
||||
acc => acc.address === selectedAccount?.value
|
||||
);
|
||||
if (!account || !transactionType || txIsLoading) {
|
||||
setState({ txIsDisabled: true });
|
||||
} else {
|
||||
setState({ txIsDisabled: false });
|
||||
}
|
||||
}, [txIsLoading, selectedTransaction, selectedAccount, accounts, setState]);
|
||||
|
||||
useEffect(() => {
|
||||
let _txFields: TxFields | undefined = transactionsData.find(
|
||||
tx => tx.TransactionType === selectedTransaction?.value
|
||||
);
|
||||
if (!_txFields) return setState({ txFields: {} });
|
||||
_txFields = { ..._txFields } as TxFields;
|
||||
|
||||
if (!_txFields.Destination) setState({ selectedDestAccount: null });
|
||||
// @ts-ignore
|
||||
delete _txFields.TransactionType;
|
||||
// @ts-ignore
|
||||
delete _txFields.Account;
|
||||
// @ts-ignore
|
||||
delete _txFields.Sequence;
|
||||
setState({ txFields: _txFields });
|
||||
}, [setState, selectedTransaction]);
|
||||
|
||||
const submitTest = useCallback(async () => {
|
||||
const account = accounts.find(
|
||||
acc => acc.address === selectedAccount?.value
|
||||
);
|
||||
const TransactionType = selectedTransaction?.value;
|
||||
if (!account || !TransactionType || txIsDisabled) return;
|
||||
|
||||
setState({ txIsLoading: true });
|
||||
// setTxIsError(null)
|
||||
try {
|
||||
let options = { ...txFields };
|
||||
|
||||
options.Destination = selectedDestAccount?.value;
|
||||
(Object.keys(options) as (keyof TxFields)[]).forEach(field => {
|
||||
let _value = options[field];
|
||||
// convert currency
|
||||
if (typeof _value === "object" && _value.type === "currency") {
|
||||
if (+_value.value) {
|
||||
options[field] = (+_value.value * 1000000 + "") as any;
|
||||
} else {
|
||||
options[field] = undefined; // 👇 💀
|
||||
}
|
||||
}
|
||||
// handle type: `json`
|
||||
if (typeof _value === "object" && _value.type === "json") {
|
||||
if (typeof _value.value === "object") {
|
||||
options[field] = _value.value as any;
|
||||
} else {
|
||||
try {
|
||||
options[field] = JSON.parse(_value.value);
|
||||
} catch (error) {
|
||||
const message = `Input error for json field '${field}': ${
|
||||
error instanceof Error ? error.message : ""
|
||||
}`;
|
||||
throw Error(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete unneccesary fields
|
||||
if (!options[field]) {
|
||||
delete options[field];
|
||||
}
|
||||
});
|
||||
const logPrefix = header ? `${header.split(".")[0]}: ` : undefined;
|
||||
await sendTransaction(
|
||||
account,
|
||||
{
|
||||
TransactionType,
|
||||
...options,
|
||||
},
|
||||
{ logPrefix }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (error instanceof Error) {
|
||||
state.transactionLogs.push({ type: "error", message: error.message });
|
||||
}
|
||||
}
|
||||
setState({ txIsLoading: false });
|
||||
}, [
|
||||
header,
|
||||
setState,
|
||||
selectedAccount?.value,
|
||||
selectedDestAccount?.value,
|
||||
selectedTransaction?.value,
|
||||
accounts,
|
||||
txFields,
|
||||
txIsDisabled,
|
||||
]);
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setState({});
|
||||
}, [setState]);
|
||||
|
||||
const handleSetAccount = (acc: SelectOption) => {
|
||||
setState({ selectedAccount: acc });
|
||||
streamState.selectedAccount = acc;
|
||||
};
|
||||
|
||||
const usualFields = ["TransactionType", "Amount", "Account", "Destination"];
|
||||
const otherFields = Object.keys(txFields).filter(
|
||||
k => !usualFields.includes(k)
|
||||
) as OtherFields;
|
||||
return (
|
||||
<Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
|
||||
<Container
|
||||
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",
|
||||
mt: "1px",
|
||||
pr: "1px",
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
Transaction type:{" "}
|
||||
</Text>
|
||||
<Select
|
||||
instanceId="transactionsType"
|
||||
placeholder="Select transaction type"
|
||||
options={transactionsOptions}
|
||||
hideSelectedOptions
|
||||
css={{ width: "70%" }}
|
||||
value={selectedTransaction}
|
||||
onChange={(tx: any) => setState({ selectedTransaction: tx })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
mb: "$3",
|
||||
pr: "1px",
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
Account:{" "}
|
||||
</Text>
|
||||
<Select
|
||||
instanceId="from-account"
|
||||
placeholder="Select your account"
|
||||
css={{ width: "70%" }}
|
||||
options={accountOptions}
|
||||
value={selectedAccount}
|
||||
onChange={(acc: any) => handleSetAccount(acc)} // TODO make react-select have correct types for acc
|
||||
/>
|
||||
</Flex>
|
||||
{txFields.Amount !== undefined && (
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
mb: "$3",
|
||||
pr: "1px",
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
Amount (XRP):{" "}
|
||||
</Text>
|
||||
<Input
|
||||
value={txFields.Amount.value}
|
||||
onChange={e =>
|
||||
setState({
|
||||
txFields: {
|
||||
...txFields,
|
||||
Amount: { type: "currency", value: e.target.value },
|
||||
},
|
||||
})
|
||||
}
|
||||
css={{ width: "70%", flex: "inherit" }}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{txFields.Destination !== undefined && (
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
mb: "$3",
|
||||
pr: "1px",
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
Destination account:{" "}
|
||||
</Text>
|
||||
<Select
|
||||
instanceId="to-account"
|
||||
placeholder="Select the destination account"
|
||||
css={{ width: "70%" }}
|
||||
options={destAccountOptions}
|
||||
value={selectedDestAccount}
|
||||
isClearable
|
||||
onChange={(acc: any) => setState({ selectedDestAccount: acc })}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{otherFields.map(field => {
|
||||
let _value = txFields[field];
|
||||
let value = typeof _value === "object" ? _value.value : _value;
|
||||
value =
|
||||
typeof value === "object"
|
||||
? JSON.stringify(value)
|
||||
: value?.toLocaleString();
|
||||
let isCurrency =
|
||||
typeof _value === "object" && _value.type === "currency";
|
||||
return (
|
||||
<Flex
|
||||
key={field}
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
mb: "$3",
|
||||
pr: "1px",
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
{field + (isCurrency ? " (XRP)" : "")}:{" "}
|
||||
</Text>
|
||||
<Input
|
||||
value={value}
|
||||
onChange={e =>
|
||||
setState({
|
||||
txFields: {
|
||||
...txFields,
|
||||
[field]:
|
||||
typeof _value === "object"
|
||||
? { ..._value, value: e.target.value }
|
||||
: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
css={{ width: "70%", flex: "inherit" }}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Container>
|
||||
<Flex
|
||||
row
|
||||
css={{
|
||||
justifyContent: "space-between",
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
mb: "$1"
|
||||
}}
|
||||
>
|
||||
<Button outline>VIEW AS JSON</Button>
|
||||
<Flex row>
|
||||
<Button onClick={resetState} outline css={{ mr: "$3" }}>
|
||||
RESET
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={submitTest}
|
||||
isLoading={txIsLoading}
|
||||
disabled={txIsDisabled}
|
||||
>
|
||||
<Play weight="bold" size="16px" />
|
||||
RUN TEST
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Transaction;
|
||||
@@ -4,7 +4,7 @@ export { default as Container } from "./Container";
|
||||
export { default as Heading } from "./Heading";
|
||||
export { default as Stack } from "./Stack";
|
||||
export { default as Text } from "./Text";
|
||||
export { default as Input } from "./Input";
|
||||
export { default as Input, Label } from "./Input";
|
||||
export { default as Select } from "./Select";
|
||||
export * from "./Tabs";
|
||||
export * from "./AlertDialog";
|
||||
|
||||
1
next-env.d.ts
vendored
1
next-env.d.ts
vendored
@@ -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
|
||||
|
||||
12057
package-lock.json
generated
Normal file
12057
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -12,13 +12,17 @@
|
||||
"dependencies": {
|
||||
"@codingame/monaco-jsonrpc": "^0.3.1",
|
||||
"@codingame/monaco-languageclient": "^0.17.0",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@monaco-editor/react": "^4.4.1",
|
||||
"@octokit/core": "^3.5.1",
|
||||
"@radix-ui/colors": "^0.1.7",
|
||||
"@radix-ui/react-alert-dialog": "^0.1.1",
|
||||
"@radix-ui/react-dialog": "^0.1.1",
|
||||
"@radix-ui/react-dropdown-menu": "^0.1.1",
|
||||
"@radix-ui/react-id": "^0.1.1",
|
||||
"@radix-ui/react-label": "^0.1.5",
|
||||
"@radix-ui/react-popover": "^0.1.6",
|
||||
"@radix-ui/react-switch": "^0.1.5",
|
||||
"@radix-ui/react-tooltip": "^0.1.7",
|
||||
"@stitches/react": "^1.2.6-0",
|
||||
"base64-js": "^1.5.1",
|
||||
"dinero.js": "^1.9.1",
|
||||
@@ -27,10 +31,11 @@
|
||||
"javascript-time-ago": "^2.3.11",
|
||||
"jszip": "^3.7.1",
|
||||
"lodash.uniqby": "^4.7.0",
|
||||
"monaco-editor": "^0.30.1",
|
||||
"lodash.xor": "^4.5.0",
|
||||
"monaco-editor": "^0.33.0",
|
||||
"next": "^12.0.4",
|
||||
"next-auth": "^4.0.0-beta.5",
|
||||
"next-themes": "^0.0.15",
|
||||
"next-themes": "^0.1.1",
|
||||
"normalize-url": "^7.0.2",
|
||||
"octokit": "^1.7.0",
|
||||
"pako": "^2.0.4",
|
||||
@@ -40,6 +45,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 +59,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 +74,4 @@
|
||||
"raw-loader": "^4.0.2",
|
||||
"typescript": "4.4.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ import state from "../state";
|
||||
import TimeAgo from "javascript-time-ago";
|
||||
import en from "javascript-time-ago/locale/en.json";
|
||||
import { useSnapshot } from "valtio";
|
||||
TimeAgo.addDefaultLocale(en);
|
||||
|
||||
TimeAgo.setDefaultLocale(en.locale);
|
||||
TimeAgo.addLocale(en);
|
||||
|
||||
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
|
||||
const router = useRouter();
|
||||
@@ -110,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}>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
18
pages/api/proxy.ts
Normal file
18
pages/api/proxy.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
try {
|
||||
const { url, opts } = req.body
|
||||
const r = await fetch(url, opts);
|
||||
if (!r.ok) throw (r.statusText)
|
||||
|
||||
const data = await r.json()
|
||||
return res.json(data)
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
return res.status(500).json({ message: "Something went wrong!" })
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Label } from "@radix-ui/react-label";
|
||||
import { Switch, SwitchThumb } from "../../components/Switch";
|
||||
import type { NextPage } from "next";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Play } from "phosphor-react";
|
||||
import { Gear, Play } from "phosphor-react";
|
||||
import Hotkeys from "react-hot-keys";
|
||||
import Split from "react-split";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { ButtonGroup, Flex } from "../../components";
|
||||
import Box from "../../components/Box";
|
||||
import Button from "../../components/Button";
|
||||
import Popover from "../../components/Popover";
|
||||
import state from "../../state";
|
||||
import { compileCode } from "../../state/actions";
|
||||
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
|
||||
|
||||
import { styled } from "../../stitches.config";
|
||||
|
||||
const HooksEditor = dynamic(() => import("../../components/HooksEditor"), {
|
||||
ssr: false,
|
||||
@@ -19,6 +23,181 @@ const LogBox = dynamic(() => import("../../components/LogBox"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const OptimizationText = () => (
|
||||
<span>
|
||||
Specify which optimization level to use for compiling. For example -O0 means
|
||||
“no optimization”: this level compiles the fastest and generates the most
|
||||
debuggable code. -O2 means moderate level of optimization which enables most
|
||||
optimizations. Read more about the options from{" "}
|
||||
<a
|
||||
className="link"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
href="https://clang.llvm.org/docs/CommandGuide/clang.html#cmdoption-o0"
|
||||
>
|
||||
clang documentation
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
);
|
||||
|
||||
const StyledOptimizationText = styled(OptimizationText, {
|
||||
color: "$mauve12 !important",
|
||||
fontSize: "200px",
|
||||
"span a.link": {
|
||||
color: "red",
|
||||
},
|
||||
});
|
||||
|
||||
const CompilerSettings = () => {
|
||||
const snap = useSnapshot(state);
|
||||
return (
|
||||
<Flex css={{ minWidth: 200, flexDirection: "column", gap: "$5" }}>
|
||||
<Box>
|
||||
<Label
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
Optimization level{" "}
|
||||
<Popover
|
||||
css={{
|
||||
maxWidth: "240px",
|
||||
lineHeight: "1.3",
|
||||
a: {
|
||||
color: "$purple11",
|
||||
},
|
||||
".dark &": {
|
||||
backgroundColor: "$black !important",
|
||||
|
||||
".arrow": {
|
||||
fill: "$colors$black",
|
||||
},
|
||||
},
|
||||
}}
|
||||
content={<StyledOptimizationText />}
|
||||
>
|
||||
<Flex
|
||||
css={{
|
||||
position: "relative",
|
||||
top: "-1px",
|
||||
ml: "$1",
|
||||
backgroundColor: "$mauve8",
|
||||
borderRadius: "$full",
|
||||
cursor: "pointer",
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
?
|
||||
</Flex>
|
||||
</Popover>
|
||||
</Label>
|
||||
<ButtonGroup css={{ mt: "$2", fontFamily: "$monospace" }}>
|
||||
<Button
|
||||
css={{ fontFamily: "$monospace" }}
|
||||
outline={snap.compileOptions.optimizationLevel !== "-O0"}
|
||||
onClick={() => (state.compileOptions.optimizationLevel = "-O0")}
|
||||
>
|
||||
-O0
|
||||
</Button>
|
||||
<Button
|
||||
css={{ fontFamily: "$monospace" }}
|
||||
outline={snap.compileOptions.optimizationLevel !== "-O1"}
|
||||
onClick={() => (state.compileOptions.optimizationLevel = "-O1")}
|
||||
>
|
||||
-O1
|
||||
</Button>
|
||||
<Button
|
||||
css={{ fontFamily: "$monospace" }}
|
||||
outline={snap.compileOptions.optimizationLevel !== "-O2"}
|
||||
onClick={() => (state.compileOptions.optimizationLevel = "-O2")}
|
||||
>
|
||||
-O2
|
||||
</Button>
|
||||
<Button
|
||||
css={{ fontFamily: "$monospace" }}
|
||||
outline={snap.compileOptions.optimizationLevel !== "-O3"}
|
||||
onClick={() => (state.compileOptions.optimizationLevel = "-O3")}
|
||||
>
|
||||
-O3
|
||||
</Button>
|
||||
<Button
|
||||
css={{ fontFamily: "$monospace" }}
|
||||
outline={snap.compileOptions.optimizationLevel !== "-O4"}
|
||||
onClick={() => (state.compileOptions.optimizationLevel = "-O4")}
|
||||
>
|
||||
-O4
|
||||
</Button>
|
||||
<Button
|
||||
css={{ fontFamily: "$monospace" }}
|
||||
outline={snap.compileOptions.optimizationLevel !== "-Os"}
|
||||
onClick={() => (state.compileOptions.optimizationLevel = "-Os")}
|
||||
>
|
||||
-Os
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Box>
|
||||
<Box css={{ flexDirection: "column" }}>
|
||||
<Label
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
Clean WASM{" "}
|
||||
<Popover
|
||||
css={{
|
||||
maxWidth: "240px",
|
||||
lineHeight: "1.3",
|
||||
a: {
|
||||
color: "$purple11",
|
||||
},
|
||||
".dark &": {
|
||||
backgroundColor: "$black !important",
|
||||
|
||||
".arrow": {
|
||||
fill: "$colors$black",
|
||||
},
|
||||
},
|
||||
}}
|
||||
content="Cleaner removes unwanted compiler-provided exports and functions from a wasm binary to make it (more) suitable for being used as a Hook"
|
||||
>
|
||||
<Flex
|
||||
css={{
|
||||
position: "relative",
|
||||
top: "-1px",
|
||||
mx: "$1",
|
||||
backgroundColor: "$mauve8",
|
||||
borderRadius: "$full",
|
||||
cursor: "pointer",
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
?
|
||||
</Flex>
|
||||
</Popover>
|
||||
</Label>
|
||||
<Switch
|
||||
css={{ mt: "$2" }}
|
||||
checked={snap.compileOptions.strip}
|
||||
onCheckedChange={(checked) => {
|
||||
state.compileOptions.strip = checked;
|
||||
}}
|
||||
>
|
||||
<SwitchThumb />
|
||||
</Switch>
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const Home: NextPage = () => {
|
||||
const snap = useSnapshot(state);
|
||||
|
||||
@@ -42,12 +221,7 @@ const Home: NextPage = () => {
|
||||
!snap.compiling && snap.files.length && compileCode(snap.active)
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="primary"
|
||||
uppercase
|
||||
disabled={!snap.files.length}
|
||||
isLoading={snap.compiling}
|
||||
onClick={() => compileCode(snap.active)}
|
||||
<Flex
|
||||
css={{
|
||||
position: "absolute",
|
||||
bottom: "$4",
|
||||
@@ -55,11 +229,25 @@ const Home: NextPage = () => {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
cursor: "pointer",
|
||||
gap: "$2",
|
||||
}}
|
||||
>
|
||||
<Play weight="bold" size="16px" />
|
||||
Compile to Wasm
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
uppercase
|
||||
disabled={!snap.files.length}
|
||||
isLoading={snap.compiling}
|
||||
onClick={() => compileCode(snap.active)}
|
||||
>
|
||||
<Play weight="bold" size="16px" />
|
||||
Compile to Wasm
|
||||
</Button>
|
||||
<Popover content={<CompilerSettings />}>
|
||||
<Button variant="primary" css={{ px: "10px" }}>
|
||||
<Gear size="16px" />
|
||||
</Button>
|
||||
</Popover>
|
||||
</Flex>
|
||||
</Hotkeys>
|
||||
)}
|
||||
</main>
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import { Play } from "phosphor-react";
|
||||
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
|
||||
} from "../../components";
|
||||
import transactionsData from "../../content/transactions.json";
|
||||
import { Box, Container, Flex, Tab, Tabs } from "../../components";
|
||||
import Transaction from "../../components/Transaction";
|
||||
import state from "../../state";
|
||||
import { sendTransaction } from "../../state/actions";
|
||||
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
|
||||
import { transactionsState, modifyTransaction } from "../../state";
|
||||
|
||||
const DebugStream = dynamic(() => import("../../components/DebugStream"), {
|
||||
ssr: false,
|
||||
@@ -24,328 +18,10 @@ const Accounts = dynamic(() => import("../../components/Accounts"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// type SelectOption<T> = { value: T, label: string };
|
||||
type TxFields = Omit<
|
||||
typeof transactionsData[0],
|
||||
"Account" | "Sequence" | "TransactionType"
|
||||
>;
|
||||
type OtherFields = (keyof Omit<TxFields, "Destination">)[];
|
||||
|
||||
interface Props {
|
||||
header?: string;
|
||||
}
|
||||
|
||||
const Transaction: FC<Props> = ({ header, ...props }) => {
|
||||
const snap = useSnapshot(state);
|
||||
|
||||
const transactionsOptions = transactionsData.map((tx) => ({
|
||||
value: tx.TransactionType,
|
||||
label: tx.TransactionType,
|
||||
}));
|
||||
const [selectedTransaction, setSelectedTransaction] = useState<
|
||||
typeof transactionsOptions[0] | null
|
||||
>(null);
|
||||
|
||||
const accountOptions = snap.accounts.map((acc) => ({
|
||||
label: acc.name,
|
||||
value: acc.address,
|
||||
}));
|
||||
const [selectedAccount, setSelectedAccount] = useState<
|
||||
typeof accountOptions[0] | null
|
||||
>(null);
|
||||
|
||||
const destAccountOptions = snap.accounts
|
||||
.map((acc) => ({
|
||||
label: acc.name,
|
||||
value: acc.address,
|
||||
}))
|
||||
.filter((acc) => acc.value !== selectedAccount?.value);
|
||||
const [selectedDestAccount, setSelectedDestAccount] = useState<
|
||||
typeof destAccountOptions[0] | null
|
||||
>(null);
|
||||
|
||||
const [txIsLoading, setTxIsLoading] = useState(false);
|
||||
const [txIsDisabled, setTxIsDisabled] = useState(false);
|
||||
const [txFields, setTxFields] = useState<TxFields>({});
|
||||
|
||||
useEffect(() => {
|
||||
const transactionType = selectedTransaction?.value;
|
||||
const account = snap.accounts.find(
|
||||
(acc) => acc.address === selectedAccount?.value
|
||||
);
|
||||
if (!account || !transactionType || txIsLoading) {
|
||||
setTxIsDisabled(true);
|
||||
} else {
|
||||
setTxIsDisabled(false);
|
||||
}
|
||||
}, [txIsLoading, selectedTransaction, selectedAccount, snap.accounts]);
|
||||
|
||||
useEffect(() => {
|
||||
let _txFields: TxFields | undefined = transactionsData.find(
|
||||
(tx) => tx.TransactionType === selectedTransaction?.value
|
||||
);
|
||||
if (!_txFields) return setTxFields({});
|
||||
_txFields = { ..._txFields } as TxFields;
|
||||
|
||||
setSelectedDestAccount(null);
|
||||
// @ts-ignore
|
||||
delete _txFields.TransactionType;
|
||||
// @ts-ignore
|
||||
delete _txFields.Account;
|
||||
// @ts-ignore
|
||||
delete _txFields.Sequence;
|
||||
setTxFields(_txFields);
|
||||
}, [selectedTransaction, setSelectedDestAccount]);
|
||||
|
||||
const submitTest = useCallback(async () => {
|
||||
const account = snap.accounts.find(
|
||||
(acc) => acc.address === selectedAccount?.value
|
||||
);
|
||||
const TransactionType = selectedTransaction?.value;
|
||||
if (!account || !TransactionType || txIsDisabled) return;
|
||||
|
||||
setTxIsLoading(true);
|
||||
// setTxIsError(null)
|
||||
try {
|
||||
let options = { ...txFields };
|
||||
|
||||
options.Destination = selectedDestAccount?.value;
|
||||
(Object.keys(options) as (keyof TxFields)[]).forEach((field) => {
|
||||
let _value = options[field];
|
||||
// convert currency
|
||||
if (typeof _value === "object" && _value.type === "currency") {
|
||||
if (+_value.value) {
|
||||
options[field] = (+_value.value * 1000000 + "") as any;
|
||||
} else {
|
||||
options[field] = undefined; // 👇 💀
|
||||
}
|
||||
}
|
||||
// handle type: `json`
|
||||
if (typeof _value === "object" && _value.type === "json") {
|
||||
if (typeof _value.value === "object") {
|
||||
options[field] = _value.value as any;
|
||||
} else {
|
||||
try {
|
||||
options[field] = JSON.parse(_value.value);
|
||||
} catch (error) {
|
||||
const message = `Input error for json field '${field}': ${
|
||||
error instanceof Error ? error.message : ""
|
||||
}`;
|
||||
throw Error(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete unneccesary fields
|
||||
if (!options[field]) {
|
||||
delete options[field];
|
||||
}
|
||||
});
|
||||
const logPrefix = header ? `${header.split(".")[0]}: ` : undefined;
|
||||
await sendTransaction(
|
||||
account,
|
||||
{
|
||||
TransactionType,
|
||||
...options,
|
||||
},
|
||||
{ logPrefix }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (error instanceof Error) {
|
||||
state.transactionLogs.push({ type: "error", message: error.message });
|
||||
}
|
||||
}
|
||||
setTxIsLoading(false);
|
||||
}, [
|
||||
header,
|
||||
selectedAccount?.value,
|
||||
selectedDestAccount?.value,
|
||||
selectedTransaction?.value,
|
||||
snap.accounts,
|
||||
txFields,
|
||||
txIsDisabled,
|
||||
]);
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setSelectedAccount(null);
|
||||
setSelectedDestAccount(null);
|
||||
setSelectedTransaction(null);
|
||||
setTxFields({});
|
||||
setTxIsDisabled(false);
|
||||
setTxIsLoading(false);
|
||||
}, []);
|
||||
|
||||
const usualFields = ["TransactionType", "Amount", "Account", "Destination"];
|
||||
const otherFields = Object.keys(txFields).filter(
|
||||
(k) => !usualFields.includes(k)
|
||||
) as OtherFields;
|
||||
return (
|
||||
<Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
|
||||
<Container
|
||||
css={{ p: "$3 0", fontSize: "$sm", height: "calc(100% - 28px)" }}
|
||||
>
|
||||
<Flex column fluid css={{ height: "100%", overflowY: "auto" }}>
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
Transaction type:{" "}
|
||||
</Text>
|
||||
<Select
|
||||
instanceId="transactionsType"
|
||||
placeholder="Select transaction type"
|
||||
options={transactionsOptions}
|
||||
hideSelectedOptions
|
||||
css={{ width: "70%" }}
|
||||
value={selectedTransaction}
|
||||
onChange={(tt) => setSelectedTransaction(tt as any)}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
Account:{" "}
|
||||
</Text>
|
||||
<Select
|
||||
instanceId="from-account"
|
||||
placeholder="Select your account"
|
||||
css={{ width: "70%" }}
|
||||
options={accountOptions}
|
||||
value={selectedAccount}
|
||||
onChange={(acc) => setSelectedAccount(acc as any)}
|
||||
/>
|
||||
</Flex>
|
||||
{txFields.Amount !== undefined && (
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
mb: "$3",
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
Amount (XRP):{" "}
|
||||
</Text>
|
||||
<Input
|
||||
value={txFields.Amount.value}
|
||||
onChange={(e) =>
|
||||
setTxFields({
|
||||
...txFields,
|
||||
Amount: { type: "currency", value: e.target.value },
|
||||
})
|
||||
}
|
||||
variant="deep"
|
||||
css={{ width: "70%", flex: "inherit", height: "$9" }}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{txFields.Destination !== undefined && (
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
mb: "$3",
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
Destination account:{" "}
|
||||
</Text>
|
||||
<Select
|
||||
instanceId="to-account"
|
||||
placeholder="Select the destination account"
|
||||
css={{ width: "70%" }}
|
||||
options={destAccountOptions}
|
||||
value={selectedDestAccount}
|
||||
isClearable
|
||||
onChange={(acc) => setSelectedDestAccount(acc as any)}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{otherFields.map((field) => {
|
||||
let _value = txFields[field];
|
||||
let value = typeof _value === "object" ? _value.value : _value;
|
||||
value =
|
||||
typeof value === "object"
|
||||
? JSON.stringify(value)
|
||||
: value?.toLocaleString();
|
||||
let isCurrency =
|
||||
typeof _value === "object" && _value.type === "currency";
|
||||
return (
|
||||
<Flex
|
||||
key={field}
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
mb: "$3",
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
{field + (isCurrency ? " (XRP)" : "")}:{" "}
|
||||
</Text>
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
setTxFields({
|
||||
...txFields,
|
||||
[field]:
|
||||
typeof _value === "object"
|
||||
? { ..._value, value: e.target.value }
|
||||
: e.target.value,
|
||||
})
|
||||
}
|
||||
variant="deep"
|
||||
css={{ width: "70%", flex: "inherit", height: "$9" }}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Container>
|
||||
<Flex
|
||||
row
|
||||
css={{
|
||||
justifyContent: "space-between",
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Button outline>VIEW AS JSON</Button>
|
||||
<Flex row>
|
||||
<Button onClick={resetState} outline css={{ mr: "$3" }}>
|
||||
RESET
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={submitTest}
|
||||
isLoading={txIsLoading}
|
||||
disabled={txIsDisabled}
|
||||
>
|
||||
<Play weight="bold" size="16px" />
|
||||
RUN TEST
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
const { transactionLogs } = useSnapshot(state);
|
||||
const [tabHeaders, setTabHeaders] = useState<string[]>(["test1.json"]);
|
||||
const { transactions, activeHeader } = useSnapshot(transactionsState);
|
||||
|
||||
return (
|
||||
<Container css={{ px: 0 }}>
|
||||
<Split
|
||||
@@ -354,7 +30,7 @@ const Test = () => {
|
||||
gutterSize={4}
|
||||
gutterAlign="center"
|
||||
style={{ height: "calc(100vh - 60px)" }}
|
||||
onDragEnd={(e) => saveSplit("testVertical", e)}
|
||||
onDragEnd={e => saveSplit("testVertical", e)}
|
||||
>
|
||||
<Flex
|
||||
row
|
||||
@@ -376,23 +52,29 @@ const Test = () => {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
onDragEnd={(e) => saveSplit("testHorizontal", e)}
|
||||
onDragEnd={e => saveSplit("testHorizontal", e)}
|
||||
>
|
||||
<Box css={{ width: "55%", px: "$2" }}>
|
||||
<Tabs
|
||||
activeHeader={activeHeader}
|
||||
// TODO make header a required field
|
||||
onChangeActive={(idx, header) => {
|
||||
if (header) transactionsState.activeHeader = header;
|
||||
}}
|
||||
keepAllAlive
|
||||
forceDefaultExtension
|
||||
defaultExtension=".json"
|
||||
onCreateNewTab={(name) =>
|
||||
setTabHeaders(tabHeaders.concat(name))
|
||||
}
|
||||
onCloseTab={(index) =>
|
||||
setTabHeaders(tabHeaders.filter((_, idx) => idx !== index))
|
||||
onCreateNewTab={header => modifyTransaction(header, {})}
|
||||
onCloseTab={(idx, header) =>
|
||||
header && modifyTransaction(header, undefined)
|
||||
}
|
||||
>
|
||||
{tabHeaders.map((header) => (
|
||||
{transactions.map(({ header, state }) => (
|
||||
<Tab key={header} header={header}>
|
||||
<Transaction header={header} />
|
||||
<Transaction
|
||||
state={state}
|
||||
header={header}
|
||||
/>
|
||||
</Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
1755
patches/ripple-binary-codec+1.3.2.patch
Normal file
1755
patches/ripple-binary-codec+1.3.2.patch
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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,14 +50,16 @@ 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,
|
||||
sequence: 1,
|
||||
hooks: [],
|
||||
isLoading: false,
|
||||
version: '2'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -66,11 +68,29 @@ export const addFaucetAccount = async (showToast: boolean = false) => {
|
||||
// fetch initial faucets
|
||||
(async function fetchFaucets() {
|
||||
if (typeof window !== 'undefined') {
|
||||
if (state.accounts.length < 2) {
|
||||
if (state.accounts.length === 0) {
|
||||
await addFaucetAccount();
|
||||
setTimeout(() => {
|
||||
addFaucetAccount();
|
||||
}, 10000);
|
||||
// setTimeout(() => {
|
||||
// addFaucetAccount();
|
||||
// }, 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,9 +35,11 @@ export const compileCode = async (activeId: number) => {
|
||||
body: JSON.stringify({
|
||||
output: "wasm",
|
||||
compress: true,
|
||||
strip: state.compileOptions.strip,
|
||||
files: [
|
||||
{
|
||||
type: "c",
|
||||
options: state.compileOptions.optimizationLevel || '-O0',
|
||||
name: state.files[activeId].name,
|
||||
src: state.files[activeId].content,
|
||||
},
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { derive, sign } from "xrpl-accountlib";
|
||||
|
||||
import state, { IAccount } from "../index";
|
||||
|
||||
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
|
||||
if (!arrayBuffer) {
|
||||
return "";
|
||||
}
|
||||
if (
|
||||
typeof arrayBuffer !== "object" ||
|
||||
arrayBuffer === null ||
|
||||
typeof arrayBuffer.byteLength !== "number"
|
||||
) {
|
||||
throw new TypeError("Expected input to be an ArrayBuffer");
|
||||
}
|
||||
|
||||
var view = new Uint8Array(arrayBuffer);
|
||||
var result = "";
|
||||
var value;
|
||||
|
||||
for (var i = 0; i < view.length; i++) {
|
||||
value = view[i].toString(16);
|
||||
result += value.length === 1 ? "0" + value : value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* deployHook function turns the wasm binary into
|
||||
* hex string, signs the transaction and deploys it to
|
||||
* Hooks testnet.
|
||||
*/
|
||||
export const deployHook = async (account: IAccount & { name?: string }) => {
|
||||
if (
|
||||
!state.files ||
|
||||
state.files.length === 0 ||
|
||||
!state.files?.[state.active]?.compiledContent
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.files?.[state.active]?.compiledContent) {
|
||||
return;
|
||||
}
|
||||
if (!state.client) {
|
||||
return;
|
||||
}
|
||||
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",
|
||||
};
|
||||
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;
|
||||
}
|
||||
try {
|
||||
const submitRes = await state.client.send({
|
||||
command: "submit",
|
||||
tx_blob: signedTransaction,
|
||||
});
|
||||
if (submitRes.engine_result === "tesSUCCESS") {
|
||||
state.deployLogs.push({
|
||||
type: "success",
|
||||
message: "Hook deployed successfully ✅",
|
||||
});
|
||||
state.deployLogs.push({
|
||||
type: "success",
|
||||
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`,
|
||||
});
|
||||
} else {
|
||||
state.deployLogs.push({
|
||||
type: "error",
|
||||
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message}`,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
state.deployLogs.push({
|
||||
type: "error",
|
||||
message: "Error occured while deploying",
|
||||
});
|
||||
}
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
257
state/actions/deployHook.tsx
Normal file
257
state/actions/deployHook.tsx
Normal file
@@ -0,0 +1,257 @@
|
||||
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";
|
||||
import { Link } from "../../components";
|
||||
import { ref } from "valtio";
|
||||
|
||||
export const sha256 = 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 toHex(str: string) {
|
||||
var result = "";
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
result += str.charCodeAt(i).toString(16);
|
||||
}
|
||||
return result.toUpperCase();
|
||||
}
|
||||
|
||||
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
|
||||
if (!arrayBuffer) {
|
||||
return "";
|
||||
}
|
||||
if (
|
||||
typeof arrayBuffer !== "object" ||
|
||||
arrayBuffer === null ||
|
||||
typeof arrayBuffer.byteLength !== "number"
|
||||
) {
|
||||
throw new TypeError("Expected input to be an ArrayBuffer");
|
||||
}
|
||||
|
||||
var view = new Uint8Array(arrayBuffer);
|
||||
var result = "";
|
||||
var value;
|
||||
|
||||
for (var i = 0; i < view.length; i++) {
|
||||
value = view[i].toString(16);
|
||||
result += value.length === 1 ? "0" + value : value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* deployHook function turns the wasm binary into
|
||||
* hex string, signs the transaction and deploys it to
|
||||
* Hooks testnet.
|
||||
*/
|
||||
export const deployHook = async (
|
||||
account: IAccount & { name?: string },
|
||||
data: SetHookData
|
||||
) => {
|
||||
if (
|
||||
!state.files ||
|
||||
state.files.length === 0 ||
|
||||
!state.files?.[state.active]?.compiledContent
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.files?.[state.active]?.compiledContent) {
|
||||
return;
|
||||
}
|
||||
if (!state.client) {
|
||||
return;
|
||||
}
|
||||
const HookNamespace = (await sha256(data.HookNamespace)).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
|
||||
)?.map(aa => ({
|
||||
HookParameter: {
|
||||
HookParameterName: toHex(aa.HookParameter.HookParameterName || ""),
|
||||
HookParameterValue: aa.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",
|
||||
Sequence: account.sequence,
|
||||
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(
|
||||
acc => acc.address === account.address
|
||||
);
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = true;
|
||||
}
|
||||
let submitRes;
|
||||
try {
|
||||
submitRes = await state.client.send({
|
||||
command: "submit",
|
||||
tx_blob: signedTransaction,
|
||||
});
|
||||
|
||||
if (submitRes.engine_result === "tesSUCCESS") {
|
||||
state.deployLogs.push({
|
||||
type: "success",
|
||||
message: "Hook deployed successfully ✅",
|
||||
});
|
||||
state.deployLogs.push({
|
||||
type: "success",
|
||||
message: ref(
|
||||
<>
|
||||
[{submitRes.engine_result}] {submitRes.engine_result_message}{" "}
|
||||
Validated ledger index:{" "}
|
||||
<Link
|
||||
as="a"
|
||||
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${submitRes.validated_ledger_index}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{submitRes.validated_ledger_index}
|
||||
</Link>
|
||||
</>
|
||||
),
|
||||
// message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`,
|
||||
});
|
||||
} else {
|
||||
state.deployLogs.push({
|
||||
type: "error",
|
||||
message: `[${submitRes.engine_result || submitRes.error}] ${
|
||||
submitRes.engine_result_message || submitRes.error_exception
|
||||
}`,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
state.deployLogs.push({
|
||||
type: "error",
|
||||
message: "Error occured while deploying",
|
||||
});
|
||||
}
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = false;
|
||||
}
|
||||
return submitRes;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteHook = async (account: IAccount & { name?: string }) => {
|
||||
if (!state.client) {
|
||||
return;
|
||||
}
|
||||
const currentAccount = state.accounts.find(
|
||||
acc => acc.address === account.address
|
||||
);
|
||||
if (currentAccount?.isLoading || !currentAccount?.hooks.length) {
|
||||
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);
|
||||
|
||||
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}`,
|
||||
});
|
||||
currentAccount.hooks = [];
|
||||
} 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;
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,6 @@ import Router from "next/router";
|
||||
import state from '../index';
|
||||
import { templateFileIds } from '../constants';
|
||||
|
||||
|
||||
const octokit = new Octokit();
|
||||
|
||||
/* Fetches Gist files from Githug Gists based on
|
||||
@@ -19,21 +18,43 @@ export const fetchFiles = (gistId: string) => {
|
||||
|
||||
octokit
|
||||
.request("GET /gists/{gist_id}", { gist_id: gistId })
|
||||
.then(res => {
|
||||
.then(async res => {
|
||||
if (!Object.values(templateFileIds).includes(gistId)) {
|
||||
return res
|
||||
}
|
||||
// in case of templates, fetch header file(s) and append to res
|
||||
return octokit.request("GET /gists/{gist_id}", { gist_id: templateFileIds.headers }).then(({ data: { files: headerFiles } }) => {
|
||||
const files = { ...res.data.files, ...headerFiles }
|
||||
res.data.files = files
|
||||
return res
|
||||
})
|
||||
let resHeaderJson;
|
||||
try {
|
||||
const resHeader = await fetch(`${process.env.NEXT_PUBLIC_COMPILE_API_BASE_URL}/api/header-files`);
|
||||
if (resHeader.ok) {
|
||||
resHeaderJson = await resHeader.json();
|
||||
const files = {
|
||||
...res.data.files,
|
||||
'hookapi.h': res.data.files?.['hookapi.h'] || { filename: 'hookapi.h', content: resHeaderJson.hookapi, language: 'C' },
|
||||
'hookmacro.h': res.data.files?.['hookmacro.h'] || { filename: 'hookmacro.h', content: resHeaderJson.hookmacro, language: 'C' },
|
||||
'sfcodes.h': res.data.files?.['sfcodes.h'] || { filename: 'sfcodes.h', content: resHeaderJson.sfcodes, language: 'C' },
|
||||
};
|
||||
res.data.files = files;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return res;
|
||||
// If you want to load templates from GIST instad, uncomment the code below and comment the code above.
|
||||
// return octokit.request("GET /gists/{gist_id}", { gist_id: templateFileIds.headers }).then(({ data: { files: headerFiles } }) => {
|
||||
// const files = { ...res.data.files, ...headerFiles }
|
||||
// console.log(headerFiles)
|
||||
// res.data.files = files
|
||||
// return res
|
||||
// })
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data.files && Object.keys(res.data.files).length > 0) {
|
||||
const files = Object.keys(res.data.files).map((filename) => ({
|
||||
name: res.data.files?.[filename]?.filename || "noname.c",
|
||||
name: res.data.files?.[filename]?.filename || "untitled.c",
|
||||
language: res.data.files?.[filename]?.language?.toLowerCase() || "",
|
||||
content: res.data.files?.[filename]?.content || "",
|
||||
}));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import toast from "react-hot-toast";
|
||||
import { derive } from "xrpl-accountlib";
|
||||
import { derive, XRPL_Account } from "xrpl-accountlib";
|
||||
|
||||
import state from '../index';
|
||||
import { names } from './addFaucetAccount';
|
||||
@@ -12,8 +12,18 @@ export const importAccount = (secret: string) => {
|
||||
if (state.accounts.find((acc) => acc.secret === secret)) {
|
||||
return toast.error("Account already added!");
|
||||
}
|
||||
const account = derive.familySeed(secret);
|
||||
if (!account.secret.familySeed) {
|
||||
let account: XRPL_Account | null = null;
|
||||
try {
|
||||
account = derive.familySeed(secret);
|
||||
} catch (err: any) {
|
||||
if (err?.message) {
|
||||
toast.error(err.message)
|
||||
} else {
|
||||
toast.error('Error occured while importing account')
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!account || !account.secret.familySeed) {
|
||||
return toast.error(`Couldn't create account!`);
|
||||
}
|
||||
state.accounts.push({
|
||||
@@ -24,6 +34,7 @@ export const importAccount = (secret: string) => {
|
||||
sequence: 1,
|
||||
hooks: [],
|
||||
isLoading: false,
|
||||
version: '2'
|
||||
});
|
||||
return toast.success("Account imported successfully!");
|
||||
};
|
||||
@@ -15,3 +15,13 @@ export const saveFile = (showToast: boolean = true) => {
|
||||
toast.success("Saved successfully", { position: "bottom-center" });
|
||||
}
|
||||
};
|
||||
|
||||
export const saveAllFiles = () => {
|
||||
const editorModels = state.editorCtx?.getModels();
|
||||
state.files.forEach(file => {
|
||||
const currentModel = editorModels?.find(model => model.uri.path.endsWith('/' + file.name))
|
||||
if (currentModel) {
|
||||
file.content = currentModel?.getValue() || '';
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ export const sendTransaction = async (account: IAccount, txOptions: TransactionO
|
||||
Fee, // TODO auto-fillable
|
||||
...opts
|
||||
};
|
||||
const currAcc = state.accounts.find(acc => acc.address === account.address);
|
||||
if (currAcc) {
|
||||
currAcc.sequence = account.sequence + 1;
|
||||
}
|
||||
const { logPrefix = '' } = options || {}
|
||||
try {
|
||||
const signedAccount = derive.familySeed(account.secret);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Octokit } from "@octokit/core";
|
||||
import Router from "next/router";
|
||||
|
||||
import state from '../index';
|
||||
import { saveAllFiles } from "./saveFile";
|
||||
|
||||
const octokit = new Octokit();
|
||||
|
||||
@@ -12,6 +13,7 @@ export const syncToGist = async (
|
||||
session?: Session | null,
|
||||
createNewGist?: boolean
|
||||
) => {
|
||||
saveAllFiles();
|
||||
let files: Record<string, { filename: string; content: string }> = {};
|
||||
state.gistLoading = true;
|
||||
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
// 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',
|
||||
'headers': '9b448e8a55fab11ef5d1274cb59f9cf3'
|
||||
'starter': '1f7d2963d9e342ea092286115274f3e3',
|
||||
'firewall': '70edec690f0de4dd315fad1f4f996d8c',
|
||||
'notary': '3d5677768fe8a54c4f6317e185d9ba66',
|
||||
'carbon': 'a9fbcaf1b816b198c7fc0f62962bebf2',
|
||||
'doubler': '56b86174aeb70b2b48eee962bad3e355',
|
||||
'peggy': 'd21298a37e1550b781682014762a567b',
|
||||
'headers': '55f639bce59a49c58c45e663776b5138'
|
||||
}
|
||||
|
||||
export const apiHeaderFiles = ['hookapi.h', 'sfcodes.h', 'hookmacro.h']
|
||||
|
||||
@@ -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;
|
||||
@@ -29,13 +34,15 @@ export interface IAccount {
|
||||
sequence: number;
|
||||
hooks: string[];
|
||||
isLoading: boolean;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface ILog {
|
||||
type: "error" | "warning" | "log" | "success";
|
||||
message: string;
|
||||
message: string | JSX.Element;
|
||||
key?: string;
|
||||
jsonData?: any,
|
||||
timestamp?: string;
|
||||
timestring?: string;
|
||||
link?: string;
|
||||
linkText?: string;
|
||||
defaultCollapsed?: boolean
|
||||
@@ -67,6 +74,10 @@ export interface IState {
|
||||
mainModalOpen: boolean;
|
||||
mainModalShowed: boolean;
|
||||
accounts: IAccount[];
|
||||
compileOptions: {
|
||||
optimizationLevel: '-O0' | '-O1' | '-O2' | '-O3' | '-O4' | '-Os';
|
||||
strip: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// let localStorageState: null | string = null;
|
||||
@@ -96,6 +107,10 @@ let initialState: IState = {
|
||||
mainModalOpen: false,
|
||||
mainModalShowed: false,
|
||||
accounts: [],
|
||||
compileOptions: {
|
||||
optimizationLevel: '-O0',
|
||||
strip: false
|
||||
}
|
||||
};
|
||||
|
||||
let localStorageAccounts: string | null = null;
|
||||
@@ -114,6 +129,8 @@ if (typeof window !== "undefined") {
|
||||
if (localStorageAccounts) {
|
||||
initialAccounts = JSON.parse(localStorageAccounts);
|
||||
}
|
||||
// filter out old accounts (they do not have version property at all)
|
||||
// initialAccounts = initialAccounts.filter(acc => acc.version === '2');
|
||||
}
|
||||
|
||||
// Initialize state
|
||||
@@ -122,9 +139,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);
|
||||
@@ -152,3 +168,5 @@ if (typeof window !== "undefined") {
|
||||
});
|
||||
}
|
||||
export default state
|
||||
|
||||
export * from './transactions'
|
||||
|
||||
59
state/transactions.ts
Normal file
59
state/transactions.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { proxy } from 'valtio';
|
||||
import { TransactionState } from '../components/Transaction';
|
||||
import { deepEqual } from '../utils/object';
|
||||
|
||||
export const defaultTransaction: TransactionState = {
|
||||
selectedTransaction: null,
|
||||
selectedAccount: null,
|
||||
selectedDestAccount: null,
|
||||
txIsLoading: false,
|
||||
txIsDisabled: false,
|
||||
txFields: {},
|
||||
};
|
||||
|
||||
export const transactionsState = proxy({
|
||||
transactions: [
|
||||
{
|
||||
header: "test1.json",
|
||||
state: defaultTransaction,
|
||||
},
|
||||
],
|
||||
activeHeader: "test1.json"
|
||||
});
|
||||
|
||||
/**
|
||||
* Simple transaction state changer
|
||||
* @param header Unique key and tab name for the transaction tab
|
||||
* @param partialTx partial transaction state, `{}` resets the state and `undefined` deletes the transaction
|
||||
*/
|
||||
export const modifyTransaction = (
|
||||
header: string,
|
||||
partialTx?: Partial<TransactionState>
|
||||
) => {
|
||||
const tx = transactionsState.transactions.find(tx => tx.header === header);
|
||||
|
||||
if (partialTx === undefined) {
|
||||
transactionsState.transactions = transactionsState.transactions.filter(
|
||||
tx => tx.header !== header
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tx) {
|
||||
transactionsState.transactions.push({
|
||||
header,
|
||||
state: {
|
||||
...defaultTransaction,
|
||||
...partialTx,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(partialTx).forEach(k => {
|
||||
// Typescript mess here, but is definetly safe!
|
||||
const s = tx.state as any;
|
||||
const p = partialTx as any;
|
||||
if (!deepEqual(s[k], p[k])) s[k] = p[k];
|
||||
});
|
||||
};
|
||||
@@ -9,16 +9,20 @@ import {
|
||||
grass,
|
||||
slate,
|
||||
mauve,
|
||||
mauveA,
|
||||
amber,
|
||||
purple,
|
||||
green,
|
||||
grayDark,
|
||||
blueDark,
|
||||
crimsonDark,
|
||||
grassDark,
|
||||
slateDark,
|
||||
mauveDark,
|
||||
mauveDarkA,
|
||||
amberDark,
|
||||
purpleDark,
|
||||
greenDark,
|
||||
red,
|
||||
redDark,
|
||||
} from "@radix-ui/colors";
|
||||
@@ -41,8 +45,10 @@ export const {
|
||||
...grass,
|
||||
...slate,
|
||||
...mauve,
|
||||
...mauveA,
|
||||
...amber,
|
||||
...purple,
|
||||
...green,
|
||||
...red,
|
||||
accent: "#9D2DFF",
|
||||
background: "$gray1",
|
||||
@@ -353,8 +359,10 @@ export const darkTheme = createTheme("dark", {
|
||||
...grassDark,
|
||||
...slateDark,
|
||||
...mauveDark,
|
||||
...mauveDarkA,
|
||||
...amberDark,
|
||||
...purpleDark,
|
||||
...greenDark,
|
||||
...redDark,
|
||||
deep: "rgb(10, 10, 10)",
|
||||
// backgroundA: transparentize(0.1, grayDark.gray1),
|
||||
|
||||
@@ -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
41
utils/hookOnCalculator.ts
Normal 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
|
||||
24
utils/object.ts
Normal file
24
utils/object.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export const deepEqual = (object1: any, object2: any) => {
|
||||
if (!isObject(object1) || !isObject(object2)) return object1 === object2
|
||||
|
||||
const keys1 = Object.keys(object1);
|
||||
const keys2 = Object.keys(object2);
|
||||
if (keys1.length !== keys2.length) {
|
||||
return false;
|
||||
}
|
||||
for (const key of keys1) {
|
||||
const val1 = object1[key];
|
||||
const val2 = object2[key];
|
||||
const areObjects = isObject(val1) && isObject(val2);
|
||||
if (
|
||||
areObjects && !deepEqual(val1, val2) ||
|
||||
!areObjects && val1 !== val2
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
export const isObject = (object: any) => {
|
||||
return object != null && typeof object === 'object';
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import hooksAccountConvBufLen from "./md/hooks-account-conv-buf-len.md";
|
||||
import hooksAccountConvPure from "./md/hooks-account-conv-pure.md";
|
||||
import hooksArrayBufLen from "./md/hooks-array-buf-len.md";
|
||||
import hooksBurdenPrereq from "./md/hooks-burden-prereq.md";
|
||||
import hooksControlStringArg from "./md/hooks-control-string-arg.md";
|
||||
import hooksDetailBufLen from "./md/hooks-detail-buf-len.md";
|
||||
import hooksDetailPrereq from "./md/hooks-detail-prereq.md";
|
||||
import hooksEmitBufLen from "./md/hooks-emit-buf-len.md";
|
||||
@@ -25,14 +26,18 @@ import hooksGuardInFor from "./md/hooks-guard-in-for.md";
|
||||
import hooksGuardInWhile from "./md/hooks-guard-in-while.md";
|
||||
import hooksHashBufLen from "./md/hooks-hash-buf-len.md";
|
||||
import hooksKeyletBufLen from "./md/hooks-keylet-buf-len.md";
|
||||
import hooksParamBufLen from "./md/hooks-param-buf-len.md";
|
||||
import hooksParamSetBufLen from "./md/hooks-param-set-buf-len.md";
|
||||
import hooksRaddrConvBufLen from "./md/hooks-raddr-conv-buf-len.md";
|
||||
import hooksRaddrConvPure from "./md/hooks-raddr-conv-pure.md";
|
||||
import hooksReleaseDefine from "./md/hooks-release-define.md";
|
||||
import hooksReserveLimit from "./md/hooks-reserve-limit.md";
|
||||
import hooksSlotHashBufLen from "./md/hooks-slot-hash-buf-len.md";
|
||||
import hooksSlotKeyletBufLen from "./md/hooks-slot-keylet-buf-len.md";
|
||||
import hooksSlotLimit from "./md/hooks-slot-limit.md";
|
||||
import hooksSlotSubLimit from "./md/hooks-slot-sub-limit.md";
|
||||
import hooksSlotTypeLimit from "./md/hooks-slot-type-limit.md";
|
||||
import hooksSkipHashBufLen from "./md/hooks-skip-hash-buf-len.md";
|
||||
import hooksStateBufLen from "./md/hooks-state-buf-len.md";
|
||||
import hooksTransactionHashBufLen from "./md/hooks-transaction-hash-buf-len.md";
|
||||
import hooksTransactionSlotLimit from "./md/hooks-transaction-slot-limit.md";
|
||||
@@ -47,6 +52,7 @@ const docs: { [key: string]: string; } = {
|
||||
"hooks-account-conv-pure": hooksAccountConvPure,
|
||||
"hooks-array-buf-len": hooksArrayBufLen,
|
||||
"hooks-burden-prereq": hooksBurdenPrereq,
|
||||
"hooks-control-string-arg": hooksControlStringArg,
|
||||
"hooks-detail-buf-len": hooksDetailBufLen,
|
||||
"hooks-detail-prereq": hooksDetailPrereq,
|
||||
"hooks-emit-buf-len": hooksEmitBufLen,
|
||||
@@ -69,14 +75,18 @@ const docs: { [key: string]: string; } = {
|
||||
"hooks-guard-in-while": hooksGuardInWhile,
|
||||
"hooks-hash-buf-len": hooksHashBufLen,
|
||||
"hooks-keylet-buf-len": hooksKeyletBufLen,
|
||||
"hooks-param-buf-len": hooksParamBufLen,
|
||||
"hooks-param-set-buf-len": hooksParamSetBufLen,
|
||||
"hooks-raddr-conv-buf-len": hooksRaddrConvBufLen,
|
||||
"hooks-raddr-conv-pure": hooksRaddrConvPure,
|
||||
"hooks-release-define": hooksReleaseDefine,
|
||||
"hooks-reserve-limit": hooksReserveLimit,
|
||||
"hooks-slot-hash-buf-len": hooksSlotHashBufLen,
|
||||
"hooks-slot-keylet-buf-len": hooksSlotKeyletBufLen,
|
||||
"hooks-slot-limit": hooksSlotLimit,
|
||||
"hooks-slot-sub-limit": hooksSlotSubLimit,
|
||||
"hooks-slot-type-limit": hooksSlotTypeLimit,
|
||||
"hooks-skip-hash-buf-len": hooksSkipHashBufLen,
|
||||
"hooks-state-buf-len": hooksStateBufLen,
|
||||
"hooks-transaction-hash-buf-len": hooksTransactionHashBufLen,
|
||||
"hooks-transaction-slot-limit": hooksTransactionSlotLimit,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# hooks-account-buf-len
|
||||
|
||||
Function `hook_account` has fixed-size account ID output.
|
||||
Function [hook_account](https://xrpl-hooks.readme.io/v2.0/reference/hook_account) has fixed-size account ID output.
|
||||
|
||||
This check warns about too-small size of its output buffer (if it's
|
||||
specified by a constant - variable parameter is ignored).
|
||||
This check warns about too-small size of its output buffer (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# hooks-account-conv-buf-len
|
||||
|
||||
Function `util_raddr` has fixed-size account ID input.
|
||||
Function [util_raddr](https://xrpl-hooks.readme.io/v2.0/reference/util_raddr) has fixed-size account ID input.
|
||||
|
||||
This check warns unless the correct size is passed in the input size
|
||||
parameter (if it's specified by a constant - variable parameter is
|
||||
ignored).
|
||||
This check warns unless the correct size is passed in the input size parameter (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
# hooks-account-conv-pure
|
||||
|
||||
Hooks identify accounts by the 20 byte account ID, which can be
|
||||
converted to an raddr using the `util_raddr` function. If the account
|
||||
ID never changes, a more efficient way to do this is precompute the
|
||||
raddr from the account ID.
|
||||
Hooks identify accounts by the 20 byte account ID, which can be converted to an raddr using the [util_raddr](https://xrpl-hooks.readme.io/v2.0/reference/util_raddr) function. If the account ID never changes, a more efficient way to do this is precompute the raddr from the account ID.
|
||||
|
||||
This check warns about calls of `util_raddr` with constant input and
|
||||
proposes to add a tracing statement showing the computed value (so
|
||||
that the user can use it to replace the call).
|
||||
This check warns about calls of `util_raddr` with constant input and proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call).
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# hooks-array-buf-len
|
||||
|
||||
Hook API `sto_subarray` requires non-empty input buffer and takes a
|
||||
parameter specifying the array index, whose value is limited - the
|
||||
sought object cannot be found if the limit is exceeded.
|
||||
Hook API [sto_subarray](https://xrpl-hooks.readme.io/v2.0/reference/sto_subarray) requires non-empty input buffer and takes a parameter specifying the array index, whose value is limited - the sought object cannot be found if the limit is exceeded.
|
||||
|
||||
This check warns about empty input as well as too-large values of the
|
||||
index specified in calls to `sto_subarray` (if they're specified by
|
||||
constants - variable parameters are ignored).
|
||||
This check warns about empty input as well as too-large values of the index specified in calls to `sto_subarray` (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# hooks-burden-prereq
|
||||
|
||||
Hook API `etxn_burden` computes transaction burden, based on (i.a.)
|
||||
the number of reserved transactions, so a call to it must be preceded
|
||||
by a call to `etxn_reserve`.
|
||||
Hook API [etxn_burden](https://xrpl-hooks.readme.io/v2.0/reference/etxn_burden) computes transaction burden, based on (i.a.) the number of reserved transactions, so a call to it must be preceded by a call to [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve).
|
||||
|
||||
5
xrpl-hooks-docs/md/hooks-control-string-arg.md
Normal file
5
xrpl-hooks-docs/md/hooks-control-string-arg.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-control-string-arg
|
||||
|
||||
Functions [accept](https://xrpl-hooks.readme.io/v2.0/reference/accept) and [rollback](https://xrpl-hooks.readme.io/v2.0/reference/rollback) take an optional string buffer stored outside the hook as its result message. This is useful for debugging but takes up space.
|
||||
|
||||
For a release version, this check warns about constant strings passed to `accept` and `rollback`.
|
||||
@@ -1,6 +1,7 @@
|
||||
# hooks-detail-buf-len
|
||||
|
||||
Function `etxn_details` has fixed-size sfEmitDetails output.
|
||||
Function [etxn_details](https://xrpl-hooks.readme.io/v2.0/reference/etxn_details) has fixed-size sfEmitDetails output.
|
||||
|
||||
This check warns about too-small size of its output buffer (if it's
|
||||
specified by a constant - variable parameter is ignored).
|
||||
This check warns about too-small size of its output buffer (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# hooks-detail-prereq
|
||||
|
||||
Hook API `etxn_details` serializes emit details, based on (i.a.) the
|
||||
number of reserved transactions, so a call to it must be preceded by a
|
||||
call to `etxn_reserve`.
|
||||
Hook API [etxn_details](https://xrpl-hooks.readme.io/v2.0/reference/etxn_details) serializes emit details, based on (i.a.) the number of reserved transactions, so a call to it must be preceded by a call to [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# hooks-emit-buf-len
|
||||
|
||||
Function `emit` has fixed-size transaction hash output.
|
||||
Function [emit](https://xrpl-hooks.readme.io/v2.0/reference/emit) has fixed-size transaction hash output.
|
||||
|
||||
This check warns about too-small size of its output buffer (if it's
|
||||
specified by a constant - variable parameter is ignored).
|
||||
This check warns about too-small size of its output buffer (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# hooks-emit-prereq
|
||||
|
||||
Before emitting a transaction using `emit` Hook API, a hook must set a
|
||||
maximal count of transactions it plans to emit, by calling
|
||||
`etxn_reserve`.
|
||||
Before emitting a transaction using [emit](https://xrpl-hooks.readme.io/v2.0/reference/emit) Hook API, a hook must set a maximal count of transactions it plans to emit, by calling [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# hooks-entry-point-recursion
|
||||
|
||||
Recursive calls are disallowed in the implementation of hook entry
|
||||
points.
|
||||
Recursive calls are disallowed in the implementation of hook entry points.
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding#no-recursion)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# hooks-entry-points-neg
|
||||
|
||||
Shows error on function definitions with unexpected (that is, neither
|
||||
`hook` nor `cbak`) names.
|
||||
Shows error on function definitions with unexpected (that is, neither `hook` nor `cbak`) names.
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/compiling-hooks#constraints)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# hooks-entry-points
|
||||
|
||||
A Hook always implements and exports exactly two functions: `cbak` and
|
||||
`hook`.
|
||||
A Hook always implements and exports exactly two functions: [cbak](https://xrpl-hooks.readme.io/v2.0/reference/cbak) and [hook](https://xrpl-hooks.readme.io/v2.0/reference/hook).
|
||||
|
||||
This check shows error on translation units that do not have them.
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/compiling-hooks)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# hooks-fee-prereq
|
||||
|
||||
Hook API `etxn_fee_base` estimates a transaction fee, based on (i.a.)
|
||||
the number of reserved transactions, so a call to it must be preceded
|
||||
by a call to `etxn_reserve`.
|
||||
Hook API [etxn_fee_base](https://xrpl-hooks.readme.io/v2.0/reference/etxn_fee_base) estimates a transaction fee, based on (i.a.) the number of reserved transactions, so a call to it must be preceded by a call to [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/hook-fees)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# hooks-field-add-buf-len
|
||||
|
||||
Emplacing a new field into STObject by calling `sto_emplace` requires
|
||||
enough space to serialize the new STObject into; the API also limits
|
||||
sizes of the old object and field.
|
||||
Emplacing a new field into STObject by calling [sto_emplace](https://xrpl-hooks.readme.io/v2.0/reference/sto_emplace) requires enough space to serialize the new STObject into; the API also limits sizes of the old object and field.
|
||||
|
||||
This check warns about insufficient output buffer space as well as
|
||||
too-large values of the inputs in calls to `sto_emplace` (if they're
|
||||
specified by constants - variable parameters are ignored).
|
||||
This check warns about insufficient output buffer space as well as too-large values of the inputs in calls to `sto_emplace` (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# hooks-field-buf-len
|
||||
|
||||
Hook API `sto_subfield` requires non-empty input buffer.
|
||||
Hook API [sto_subfield](https://xrpl-hooks.readme.io/v2.0/reference/sto_subfield) requires non-empty input buffer.
|
||||
|
||||
This check warns about empty input in calls to `sto_subfield` (if it's
|
||||
specified by a constant - variable parameter is ignored).
|
||||
This check warns about empty input in calls to `sto_subfield` (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# hooks-field-del-buf-len
|
||||
|
||||
Erasing a field from STObject by calling `sto_erase` requires enough
|
||||
space to serialize the new STObject into; the API also limits size of
|
||||
the old object.
|
||||
Erasing a field from STObject by calling [sto_erase](https://xrpl-hooks.readme.io/v2.0/reference/sto_erase) requires enough space to serialize the new STObject into; the API also limits size of the old object.
|
||||
|
||||
This check warns about insufficient output buffer space as well as
|
||||
too-large value of the input STObject in calls to `sto_erase` (if
|
||||
they're specified by constants - variable parameters are ignored).
|
||||
This check warns about insufficient output buffer space as well as too-large value of the input STObject in calls to `sto_erase` (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
# hooks-float-arith-pure
|
||||
|
||||
Hooks can compute floating-point values in XFL format by calling
|
||||
functions `float_multiply`, `float_mulratio`, `float_negate`,
|
||||
`float_sum`, `float_invert` and `float_divide` and access their
|
||||
constituent parts by calling `float_exponent`, `float_mantissa` and
|
||||
`float_sign`. If the inputs of the computation never change, a more
|
||||
efficient way to do this is to precompute it.
|
||||
Hooks can compute floating-point values in XFL format by calling functions [float_multiply](https://xrpl-hooks.readme.io/v2.0/reference/float_multiply), [float_mulratio](https://xrpl-hooks.readme.io/v2.0/reference/float_mulratio), [float_negate](https://xrpl-hooks.readme.io/v2.0/reference/float_negate), [float_sum](https://xrpl-hooks.readme.io/v2.0/reference/float_sum), [float_invert](https://xrpl-hooks.readme.io/v2.0/reference/float_invert) and [float_divide](https://xrpl-hooks.readme.io/v2.0/reference/float_divide) and access their constituent parts by calling [float_exponent](https://xrpl-hooks.readme.io/v2.0/reference/float_exponent), [float_mantissa](https://xrpl-hooks.readme.io/v2.0/reference/float_mantissa) and [float_sign](https://xrpl-hooks.readme.io/v2.0/reference/float_sign). If the inputs of the computation never change, a more efficient way to do this is to precompute it.
|
||||
|
||||
This check warns about calls of the aforementioned functions with constant inputs and in simple cases proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call). It also checks that the divisor passed to `float_divide`, `float_mulratio` and `float_invert` is not 0 (if it's specified by a constant - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
|
||||
|
||||
This check warns about calls of the aforementioned functions with
|
||||
constant inputs and in simple cases proposes to add a tracing
|
||||
statement showing the computed value (so that the user can use it to
|
||||
replace the call). It also checks that the divisor passed to
|
||||
`float_divide`, `float_mulratio` and `float_invert` is not 0 (if it's
|
||||
specified by a constant - variable parameters are ignored).
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# hooks-float-compare-pure
|
||||
|
||||
Hooks can compare floating-point values in XFL format by calling the
|
||||
`float_compare` function. If the inputs of the comparison never
|
||||
change, its result is fixed and the function need not be called.
|
||||
Hooks can compare floating-point values in XFL format by calling the [float_compare](https://xrpl-hooks.readme.io/v2.0/reference/float_compare) function. If the inputs of the comparison never change, its result is fixed and the function need not be called.
|
||||
|
||||
This check warns about calls of `float_compare` with constant inputs
|
||||
as well as invalid values of the comparison mode parameter (if it's
|
||||
specified by a constant - variable parameter is ignored).
|
||||
This check warns about calls of `float_compare` with constant inputs as well as invalid values of the comparison mode parameter (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
# hooks-float-int-pure
|
||||
|
||||
Hooks can convert floating-point values in XFL format to integers by
|
||||
calling the `float_int` function. If the inputs of this function never
|
||||
change, a more efficient way to do this is to precompute the integer
|
||||
value.
|
||||
Hooks can convert floating-point values in XFL format to integers by calling the [float_int](https://xrpl-hooks.readme.io/v2.0/reference/float_int) function. If the inputs of this function never change, a more efficient way to do this is to precompute the integer value.
|
||||
|
||||
This check warns about calls of `float_int` with constant inputs as
|
||||
well as invalid values of the decimal places parameter (if it's
|
||||
specified by a constant - variable parameter is ignored).
|
||||
This check warns about calls of `float_int` with constant inputs as well as invalid values of the decimal places parameter (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
# hooks-float-manip-pure
|
||||
|
||||
Hooks can directly manipulate floating-point values in XFL format by
|
||||
calling functions `float_exponent_set`, `float_mantissa_set` and
|
||||
`float_sign_set`. If the inputs of the update never change, a more
|
||||
efficient way to do this is to precompute it.
|
||||
Hooks can directly manipulate floating-point values in XFL format by calling functions [float_exponent_set](https://xrpl-hooks.readme.io/v2.0/reference/float_exponent_set), [float_mantissa_set](https://xrpl-hooks.readme.io/v2.0/reference/float_mantissa_set) and [float_sign_set](https://xrpl-hooks.readme.io/v2.0/reference/float_sign_set). If the inputs of the update never change, a more efficient way to do this is to precompute it.
|
||||
|
||||
This check warns about calls of the aforementioned functions with
|
||||
constant inputs and in simple cases proposes to add a tracing
|
||||
statement showing the computed value (so that the user can use it to
|
||||
replace the call). It also checks documented bounds of the second
|
||||
parameter of these functions (if it's specified by a constant -
|
||||
variable parameter is ignored).
|
||||
This check warns about calls of the aforementioned functions with constant inputs and in simple cases proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call). It also checks documented bounds of the second parameter of these functions (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# hooks-float-one-pure
|
||||
|
||||
Hooks can obtain XFL enclosing number 1 by calling the float_one
|
||||
function. Since the number never changes, a more efficient way is to
|
||||
use its precomputed value.
|
||||
Hooks can obtain XFL enclosing number 1 by calling the [float_one](https://xrpl-hooks.readme.io/v2.0/reference/float_one) function. Since the number never changes, a more efficient way is to use its precomputed value.
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
# hooks-float-pure
|
||||
|
||||
Hooks can use floating-point values in XFL format, creating them from
|
||||
mantissa and exponent by calling the `float_set` function. If the
|
||||
mantissa and exponent never change, a more efficient way to do this is
|
||||
to precompute the floating-point value.
|
||||
Hooks can use floating-point values in XFL format, creating them from mantissa and exponent by calling the [float_set](https://xrpl-hooks.readme.io/v2.0/reference/float_set) function. If the mantissa and exponent never change, a more efficient way to do this is to precompute the floating-point value.
|
||||
|
||||
This check warns about calls of `float_set` with constant inputs and
|
||||
proposes to add a tracing statement showing the computed value (so
|
||||
that the user can use it to replace the call). In the special case of
|
||||
0 mantissa and 0 exponent ("canonical 0"), a replacement value of 0 is
|
||||
proposed directly, with no need to trace it.
|
||||
This check warns about calls of `float_set` with constant inputs and proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call). In the special case of 0 mantissa and 0 exponent ("canonical 0"), a replacement value of 0 is proposed directly, with no need to trace it.
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# hooks-guard-called
|
||||
|
||||
Every hook needs to import the guard function `_g` and use it at least once.
|
||||
Every hook needs to import the guard function [_g](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding#the-guard-function) and use it at least once.
|
||||
|
||||
[Read documentation](https://xrpl-hooks.readme.io/docs/loops-and-guarding)
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# hooks-guard-in-for
|
||||
|
||||
Consider the following for-loop in C:
|
||||
A guard is a marker that must be placed in your code at the top of each loop. Consider the following for-loop in C:
|
||||
|
||||
```c
|
||||
#define GUARD(maxiter) _g(__LINE__, (maxiter)+1)
|
||||
@@ -8,5 +8,7 @@ Consider the following for-loop in C:
|
||||
for (int i = 0; GUARD(3), i < 3; ++i)
|
||||
```
|
||||
|
||||
This is the only way to satisfy the guard rule when using a for-loop
|
||||
in C.
|
||||
<BR/>
|
||||
This is the only way to satisfy the guard rule when using a for-loop in C.
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding)
|
||||
|
||||
@@ -9,3 +9,6 @@ Like for loops, while loops must have a guard in their condition:
|
||||
int i = 0;
|
||||
while (GUARD(3), i < 3)
|
||||
```
|
||||
|
||||
<BR/>
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# hooks-hash-buf-len
|
||||
|
||||
Functions `util_sha512h`, `hook_hash`, `ledger_last_hash` and `nonce`
|
||||
have fixed-size hash output.
|
||||
Functions [util_sha512h](https://xrpl-hooks.readme.io/v2.0/reference/util_sha512h), [hook_hash](https://xrpl-hooks.readme.io/v2.0/reference/hook_hash), [ledger_last_hash](https://xrpl-hooks.readme.io/v2.0/reference/ledger_last_hash) and [nonce](https://xrpl-hooks.readme.io/v2.0/reference/nonce) have fixed-size hash output.
|
||||
|
||||
This check warns about too-small size of their output buffer (if it's
|
||||
specified by a constant - variable parameter is ignored).
|
||||
This check warns about too-small size of their output buffer (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# hooks-keylet-buf-len
|
||||
|
||||
Computing a ripple keylet by calling `util_keylet` requires valid
|
||||
parameters dependent on the keylet type.
|
||||
Computing a ripple keylet by calling [util_keylet](https://xrpl-hooks.readme.io/v2.0/reference/util_keylet) requires valid parameters dependent on the keylet type.
|
||||
|
||||
This check does not fully parse these parameters, but warns about
|
||||
invalid keylet type as well as buffer sizes that cannot be valid (if
|
||||
they're specified by constants - variable parameters are ignored).
|
||||
This check does not fully parse these parameters, but warns about invalid keylet type as well as buffer sizes that cannot be valid (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
|
||||
|
||||
7
xrpl-hooks-docs/md/hooks-param-buf-len.md
Normal file
7
xrpl-hooks-docs/md/hooks-param-buf-len.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-param-buf-len
|
||||
|
||||
Function [hook_param](https://xrpl-hooks.readme.io/v2.0/reference/hook_param) expects a limited-length name input and produces fixed-size value output.
|
||||
|
||||
This check warns about invalid sizes of input and output buffers (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/parameters)
|
||||
7
xrpl-hooks-docs/md/hooks-param-set-buf-len.md
Normal file
7
xrpl-hooks-docs/md/hooks-param-set-buf-len.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-param-set-buf-len
|
||||
|
||||
Function [hook_param_set](https://xrpl-hooks.readme.io/v2.0/reference/hook_param_set) expects limited-length name, fixed-length hash and limited-length value inputs.
|
||||
|
||||
This check warns about invalid sizes of input buffers (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/parameters)
|
||||
@@ -1,8 +1,5 @@
|
||||
# hooks-raddr-conv-buf-len
|
||||
|
||||
Hook API `util_accid` has upper limit on the length of its input
|
||||
(because it expects it to be a raddr) and fixed-size account ID
|
||||
output.
|
||||
Hook API [util_accid](https://xrpl-hooks.readme.io/v2.0/reference/util_accid) has upper limit on the length of its input (because it expects it to be a raddr) and fixed-size account ID output.
|
||||
|
||||
This check warns about invalid sizes of input and output parameters
|
||||
(if they're specified by constants - variable parameters are ignored).
|
||||
This check warns about invalid sizes of input and output parameters (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
# hooks-raddr-conv-pure
|
||||
|
||||
Hooks identify accounts by the 20 byte account ID, which can be
|
||||
converted from a raddr using the `util_accid` function. If the raddr
|
||||
never changes, a more efficient way to do this is precompute the
|
||||
account-id from the raddr.
|
||||
Hooks identify accounts by the 20 byte account ID, which can be converted from a raddr using the [util_accid](https://xrpl-hooks.readme.io/v2.0/reference/util_accid) function. If the raddr never changes, a more efficient way to do this is precompute the account-id from the raddr.
|
||||
|
||||
This check warns about calls of `util_accid` with constant input and
|
||||
proposes to add a tracing statement showing the computed value (so
|
||||
that the user can use it to replace the call).
|
||||
This check warns about calls of `util_accid` with constant input and proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call).
|
||||
|
||||
5
xrpl-hooks-docs/md/hooks-release-define.md
Normal file
5
xrpl-hooks-docs/md/hooks-release-define.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-release-define
|
||||
|
||||
Hook users can define a `NDEBUG` macro to disable tracing calls at compile time - but for the definition to be effective, it must be defined before including hook-specific headers.
|
||||
|
||||
This check warns when `NDEBUG` is defined too late.
|
||||
@@ -1,9 +1,7 @@
|
||||
# hooks-reserve-limit
|
||||
|
||||
Hook API `etxn_reserve` takes a parameter specifying the number of
|
||||
transactions intended to emit from the calling hook. Value of this
|
||||
parameter is limited, and the function fails if the limit is exceeded.
|
||||
Hook API [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve) takes a parameter specifying the number of transactions intended to emit from the calling hook. Value of this parameter is limited, and the function fails if the limit is exceeded.
|
||||
|
||||
This check warns about too-large values of the number of reserved
|
||||
transactions (if they're specified by a constant - variable parameter
|
||||
is ignored).
|
||||
This check warns about too-large values of the number of reserved transactions (if they're specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
|
||||
|
||||
5
xrpl-hooks-docs/md/hooks-skip-hash-buf-len.md
Normal file
5
xrpl-hooks-docs/md/hooks-skip-hash-buf-len.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-skip-hash-buf-len
|
||||
|
||||
Function [hook_skip](https://xrpl-hooks.readme.io/v2.0/reference/hook_skip) has fixed-size canonical hash input.
|
||||
|
||||
This check warns about invalid size of its input buffer (if it's specified by a constant - variable parameter is ignored).
|
||||
@@ -1,7 +1,7 @@
|
||||
# hooks-slot-hash-buf-len
|
||||
|
||||
Function `slot_id` has fixed-size canonical hash output.
|
||||
Function [slot_id](https://xrpl-hooks.readme.io/v2.0/reference/slot_id) has fixed-size canonical hash output.
|
||||
|
||||
This check warns about too-small size of its output buffer as well as
|
||||
invalid values of the slot number parameter (if they're specified by
|
||||
constants - variable parameters are ignored).
|
||||
This check warns about too-small size of its output buffer as well as invalid values of the slot number parameter (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# hooks-slot-keylet-buf-len
|
||||
|
||||
Function `slot_set` has structured keylet input.
|
||||
Function [slot_set](https://xrpl-hooks.readme.io/v2.0/reference/slot_set) has structured keylet input.
|
||||
|
||||
This check does not parse the input, but warns about its sizes that
|
||||
cannot be valid as well as invalid values of the slot number parameter
|
||||
(if they're specified by constants - variable parameters are ignored).
|
||||
This check does not parse the input, but warns about its sizes that cannot be valid as well as invalid values of the slot number parameter (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# hooks-slot-limit
|
||||
|
||||
Hook APIs `slot`, `slot_count`, `slot_clear`, `slot_size`,
|
||||
`slot_float` and `trace_slot` take a parameter specifying the accessed
|
||||
slot number. Value of this parameter is limited, and the functions
|
||||
fail if the limit is exceeded.
|
||||
Hook APIs [slot](https://xrpl-hooks.readme.io/v2.0/reference/slot), [slot_count](https://xrpl-hooks.readme.io/v2.0/reference/slot_count), [slot_clear](https://xrpl-hooks.readme.io/v2.0/reference/slot_clear), [slot_size](https://xrpl-hooks.readme.io/v2.0/reference/slot_size), [slot_float](https://xrpl-hooks.readme.io/v2.0/reference/slot_float) and [trace_slot](https://xrpl-hooks.readme.io/v2.0/reference/trace_slot) take a parameter specifying the accessed slot number. Value of this parameter is limited, and the functions fail if the limit is exceeded.
|
||||
|
||||
This check warns about too-large values of the slot number (if it's
|
||||
specified by a constant - variable parameter is ignored).
|
||||
This check warns about too-large values of the slot number (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# hooks-slot-sub-limit
|
||||
|
||||
Hook APIs `slot_subarray` and `slot_subfield` take parameters
|
||||
specifying parent and child slot numbers. Values of these parameters
|
||||
are limited, and the functions fail if the limit is exceeded.
|
||||
Hook APIs [slot_subarray](https://xrpl-hooks.readme.io/v2.0/reference/slot_subarray) and [slot_subfield](https://xrpl-hooks.readme.io/v2.0/reference/slot_subfield) take parameters specifying parent and child slot numbers. Values of these parameters are limited, and the functions fail if the limit is exceeded.
|
||||
|
||||
This check warns about too-large values of the slot numbers (if
|
||||
they're specified by a constant - variable parameters are ignored).
|
||||
This check warns about too-large values of the slot numbers (if they're specified by a constant - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# hooks-slot-type-limit
|
||||
|
||||
Hook API `slot_type` takes a parameter specifying the accessed slot
|
||||
number. Value of this parameter is limited, and the function fails if
|
||||
the limit is exceeded.
|
||||
Hook API [slot_type](https://xrpl-hooks.readme.io/v2.0/reference/slot_type) takes a parameter specifying the accessed slot number. Value of this parameter is limited, and the function fails if the limit is exceeded.
|
||||
|
||||
This check warns about too-large values of the slot number as well as
|
||||
invalid values of the flags parameter (if they're specified by
|
||||
constants - variable parameters are ignored).
|
||||
This check warns about too-large values of the slot number as well as invalid values of the flags parameter (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# hooks-state-buf-len
|
||||
|
||||
Functions state and state_set accept fixed-size Hook State key.
|
||||
Functions [state](https://xrpl-hooks.readme.io/v2.0/reference/state) and [state_set](https://xrpl-hooks.readme.io/v2.0/reference/state_set) accept fixed-size Hook State key.
|
||||
|
||||
This check warns about invalid size of its input buffer (if it's
|
||||
specified by a constant - variable parameter is ignored).
|
||||
This check warns about invalid size of its input buffer (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/state-management)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# hooks-transaction-hash-buf-len
|
||||
|
||||
Function `otxn_id` has fixed-size canonical hash output.
|
||||
Function [otxn_id](https://xrpl-hooks.readme.io/v2.0/reference/otxn_id) has fixed-size canonical hash output.
|
||||
|
||||
This check warns about too-small size of its output buffer (if it's
|
||||
specified by a constant - variable parameter is ignored).
|
||||
This check warns about too-small size of its output buffer (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# hooks-transaction-slot-limit
|
||||
|
||||
Function `otxn_slot` takes a parameter specifying the accessed slot
|
||||
number. Value of this parameter is limited, and the function fails if
|
||||
the limit is exceeded.
|
||||
Function [otxn_slot](https://xrpl-hooks.readme.io/v2.0/reference/otxn_slot) takes a parameter specifying the accessed slot number. Value of this parameter is limited, and the function fails if the limit is exceeded.
|
||||
|
||||
This check warns about too-large values of the slot number (if it's
|
||||
specified by a constant - variable parameter is ignored).
|
||||
This check warns about too-large values of the slot number (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# hooks-validate-buf-len
|
||||
|
||||
Hook API `sto_validate` requires non-empty input buffer.
|
||||
Hook API [sto_validate](https://xrpl-hooks.readme.io/v2.0/reference/sto_validate) requires non-empty input buffer.
|
||||
|
||||
This check warns about empty input in calls to `sto_validate` (if it's
|
||||
specified by a constant - variable parameter is ignored).
|
||||
This check warns about empty input in calls to `sto_validate` (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
# hooks-verify-buf-len
|
||||
|
||||
Verifying a cryptographic signature by calling `util_verify` requires
|
||||
valid public key & data signature.
|
||||
Verifying a cryptographic signature by calling [util_verify](https://xrpl-hooks.readme.io/v2.0/reference/util_verify) requires valid public key & data signature.
|
||||
|
||||
This check does not fully parse these parameters, but warns about
|
||||
their sizes that cannot be valid (if they're specified by constants -
|
||||
variable parameters are ignored).
|
||||
This check does not fully parse these parameters, but warns about their sizes that cannot be valid (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
Reference in New Issue
Block a user