Compare commits

...

87 Commits

Author SHA1 Message Date
Valtteri Karesto
5aeed7c246 Few more changes to deleteHook function 2022-03-29 15:23:30 +03:00
Valtteri Karesto
8d03edc299 Fixes issue #148 2022-03-29 14:03:06 +03:00
Valtteri Karesto
4519906b78 hotfix/remove-console-log 2022-03-28 15:35:27 +03:00
Valtteri Karesto
88a47c49a4 Merge pull request #149 from eqlabs/fix/fix-hook-params
Convert hook params to hex blobs
2022-03-28 15:33:23 +03:00
Valtteri Karesto
1ab03f9bed Fix types 2022-03-28 15:22:10 +03:00
Valtteri Karesto
84ff667135 Convert hook params to hex blobs 2022-03-28 15:20:14 +03:00
Valtteri Karesto
0d10e782f3 Ask faucets account only once if you dont have one 2022-03-25 11:35:23 +02:00
Valtteri Karesto
84e6763495 hotfix/keep-accounts 2022-03-25 10:49:29 +02:00
Vaclav Barta
7ffcfd694d Feature/new hook doc (#144)
* added hooks-param-buf-len doc file

* showing help for hooks-param-buf-len

* added hooks-param-set-buf-len
2022-03-25 09:23:53 +01:00
Valtteri Karesto
77e4917d38 Merge pull request #145 from eqlabs/hotfix/fix-accounts
Fix problem with accounts
2022-03-25 10:21:47 +02:00
Valtteri Karesto
e4238a40cc Fix problem with accounts 2022-03-25 10:18:55 +02:00
Valtteri Karesto
42c0b20512 Merge pull request #136 from eqlabs/feat/hooks-v2-preparations
Feat/hooks v2 preparations
2022-03-25 09:48:19 +02:00
Valtteri Karesto
43154ff6d8 Updated hooks templates 2022-03-25 09:41:29 +02:00
Valtteri Karesto
8197b510f9 delete hook and delete account features, #140 #47 2022-03-24 22:18:59 +02:00
Valtteri Karesto
fc7652f48e Remove logging 2022-03-24 21:22:27 +02:00
Valtteri Karesto
bd32555617 improve toast message 2022-03-24 21:21:35 +02:00
Valtteri Karesto
fc6f420e1e Add funds feature added 2022-03-24 21:20:29 +02:00
Valtteri Karesto
d3c36765de Fix select option focus color 2022-03-24 17:36:31 +02:00
Valtteri Karesto
2628a12673 Merge branch 'main' of github.com:eqlabs/xrpl-hooks-ide into feat/hooks-v2-preparations 2022-03-24 17:32:10 +02:00
Valtteri Karesto
f6c1869b5d Bring back styled logs after debug round 2022-03-24 17:31:49 +02:00
Valtteri Karesto
62c8b4f217 Merge pull request #130 from eqlabs/fix/do-not-allow-special-chars
Prevent special characters on filename
2022-03-24 16:54:59 +02:00
Valtteri Karesto
8798e5a233 Comment out code temporarily 2022-03-24 16:09:05 +02:00
Valtteri Karesto
5f7d42843c temporarily print raw debug messages 2022-03-24 16:07:02 +02:00
Valtteri Karesto
302b36dde8 Give new ids to account requests 2022-03-24 16:06:37 +02:00
Valtteri Karesto
3e7c7b1969 Updated regex 2022-03-24 14:49:30 +02:00
Valtteri Karesto
936bbc503a Updated refetch time to 5 seconds 2022-03-24 14:40:58 +02:00
Valtteri Karesto
81890c8833 Added loading state to set hook modal 2022-03-23 13:28:59 +02:00
Valtteri Karesto
50fa20c39a Fix styling of the text 2022-03-23 12:41:14 +02:00
Valtteri Karesto
11f2cffc87 Loosen up filters a bit 2022-03-23 09:52:57 +02:00
Valtteri Karesto
bbd1d162f0 Fix styling issues 2022-03-22 17:36:10 +02:00
Valtteri Karesto
b301a860bf Fix overflow problems and other styling issues 2022-03-22 17:08:38 +02:00
Valtteri Karesto
ff697b96ea reverse hookon calculation logic 2022-03-22 17:08:19 +02:00
Valtteri Karesto
48e9898e31 Add smarter filtering for debugstream 2022-03-22 17:08:00 +02:00
Valtteri Karesto
2e25242ebe Update urls 2022-03-22 17:07:41 +02:00
Valtteri Karesto
e32e07f7fd Added xor 2022-03-22 17:07:00 +02:00
Valtteri Karesto
0d2a17008e Update envs 2022-03-22 17:06:50 +02:00
Valtteri Karesto
a87b3de6c4 Add few more fixes and styling to modals 2022-03-17 19:10:27 +02:00
Valtteri Karesto
23068ff477 Style select again to support multiselect 2022-03-17 19:10:02 +02:00
Vaclav Barta
a12a5dfbac Merge pull request #138 from eqlabs/bugfix/timeago-init
alternative TimeAgo setup - terminal problems unfortunately persist, but at least the warning is gone...
2022-03-17 14:08:20 +01:00
Vaclav Barta
5a598cb091 alternative TimeAgo setup 2022-03-17 08:50:40 +01:00
Valtteri Karesto
be39054a2f Minor updates to debugstream, related to v2 2022-03-16 19:01:13 +02:00
Valtteri Karesto
0add65dd1c Update hook deployment logic for v2 2022-03-16 19:01:01 +02:00
Valtteri Karesto
82170ad4f8 update set hook functionality 2022-03-16 19:00:30 +02:00
Valtteri Karesto
af49426eb0 Move fonts to correct location 2022-03-16 19:00:15 +02:00
Valtteri Karesto
48a86e3386 Fix problems with middleware 2022-03-16 18:59:48 +02:00
Valtteri Karesto
c82c35b5a1 Use new env variables 2022-03-16 18:59:37 +02:00
Valtteri Karesto
f849be1f80 export variables 2022-03-16 18:59:21 +02:00
Valtteri Karesto
694d07fa0e Add react hook form and new env variables 2022-03-16 18:59:11 +02:00
Valtteri Karesto
b9aa3e2adc Merge branch 'main' of github.com:eqlabs/xrpl-hooks-ide into feat/hooks-v2-preparations 2022-03-14 14:37:12 +02:00
Valtteri Karesto
5b573b2379 Merge pull request #129 from eqlabs/fix/sample-list-logic
Fix modal showing up bug, issue #99
2022-03-14 14:36:57 +02:00
Valtteri Karesto
23538b1502 Fix navigation 2022-03-11 16:08:27 +02:00
Valtteri Karesto
723602ebdc Make some v2 hooks api preparations 2022-03-11 16:08:16 +02:00
Valtteri Karesto
f8fdeaf9ce Update dependencies 2022-03-11 16:07:01 +02:00
Vaclav Barta
e75b971718 Merge pull request #133 from eqlabs/update-hook-doc
closes #131
2022-03-11 08:03:19 +01:00
Vaclav Barta
11a35a5932 added link 2022-03-10 10:48:56 +01:00
Vaclav Barta
611f875761 upgraded links to https://xrpl-hooks.readme.io/v2.0/, added more 2022-03-10 10:15:20 +01:00
Vaclav Barta
a7df50c194 updated docs 2022-03-09 16:41:28 +01:00
Valtteri Karesto
0c6c60ed29 Removed unused variable 2022-03-09 16:26:10 +02:00
Vaclav Barta
e82662647f started updating docs 2022-03-09 15:25:42 +01:00
Valtteri Karesto
5490e7205a Improved the filename validation 2022-03-09 16:24:23 +02:00
Valtteri Karesto
d8e218392a Merge pull request #132 from eqlabs/feat/add-docs-about-hover-messages
Improve readme
2022-03-09 16:23:47 +02:00
Valtteri Karesto
723722df58 Merge pull request #123 from eqlabs/fix/increment-test-sequence-number
Increment sequence on every transaction
2022-03-09 15:21:53 +02:00
Valtteri Karesto
2ff85ede06 Improve readme 2022-03-09 15:19:59 +02:00
Valtteri Karesto
052a1e5b60 Prevent special characters on filename 2022-03-09 15:01:46 +02:00
Valtteri Karesto
7f8f47cb14 Fix modal showing up bug, issue #99 2022-03-09 13:27:42 +02:00
Valtteri Karesto
ddb043c104 Merge pull request #128 from eqlabs/feat/fix-account-button
Fixes issue #101
2022-03-09 12:50:50 +02:00
Valtteri Karesto
d2ad6537d7 Remove console logs 2022-03-09 12:48:53 +02:00
Valtteri Karesto
8f004ee4da Fixes issue #101 2022-03-09 12:29:20 +02:00
Valtteri Karesto
b90bf67c20 Merge pull request #120 from eqlabs/feat/add-hooks-docs
Feat/add hooks docs
2022-03-09 12:06:06 +02:00
muzamil
38a097a8f9 Merge pull request #108 from eqlabs/feat/debug-prettify
Debug stream improvements.
2022-03-08 21:07:13 +05:30
muzam1l
db0ffe999e fix line wrap and timestamp font 2022-03-08 19:18:51 +05:30
Jani Anttonen
6d88c4e546 Merge pull request #100 from eqlabs/feat/persist-splits
Persist splits
2022-03-08 14:15:49 +02:00
Valtteri Karesto
09f58f18ae Increment sequence on every transaction 2022-03-08 13:16:45 +02:00
muzam1l
ce91182c7b fix and segrregate debug stream state. 2022-03-07 17:14:53 +05:30
muzam1l
2e3a0e557e Some vertical margin in enhanced logs 2022-03-04 20:37:07 +05:30
muzam1l
6b9a9ef978 better socket error mesage 2022-03-04 15:36:48 +05:30
muzam1l
bc5bb5be39 Fix timesatmp in logs 2022-03-04 15:22:05 +05:30
muzam1l
0fe83811b9 Clickable accounts in logs and formatting fixes 2022-03-04 15:13:56 +05:30
muzam1l
c6359aa853 Add error code to close event message 2022-03-04 14:19:23 +05:30
muzam1l
c9c818c8f3 json data is now collapsible! 2022-03-03 21:03:28 +05:30
muzam1l
c521246393 debug stream state as full global 2022-03-03 16:36:42 +05:30
muzam1l
8936b34361 separate messages for debug stream error and close evensts 2022-03-03 16:16:00 +05:30
muzam1l
5993d2762f Separately format time, json and message of debug stream log. 2022-03-01 21:36:24 +05:30
JaniAnttonen
810d3b2524 Add a todo 2022-02-25 16:34:09 +02:00
JaniAnttonen
a3393ded1e Fix build 2022-02-25 15:22:09 +02:00
JaniAnttonen
17ede265b1 Remove debug logging 2022-02-25 14:39:06 +02:00
JaniAnttonen
629070edad Save split state 2022-02-25 13:50:56 +02:00
82 changed files with 3888 additions and 2364 deletions

View File

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

View File

@@ -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.

View File

@@ -1,11 +1,11 @@
import toast from "react-hot-toast";
import { useSnapshot } from "valtio";
import { ArrowSquareOut, Copy, Wallet, X } from "phosphor-react";
import { ArrowSquareOut, Copy, Trash, Wallet, X } from "phosphor-react";
import React, { useEffect, useState, FC } from "react";
import Dinero from "dinero.js";
import Button from "./Button";
import { addFaucetAccount, deployHook, importAccount } from "../state/actions";
import { addFaucetAccount, importAccount } from "../state/actions";
import state from "../state";
import Box from "./Box";
import { Container, Heading, Stack, Text, Flex } from ".";
@@ -19,6 +19,7 @@ import {
} from "./Dialog";
import { css } from "../stitches.config";
import { Input } from "./Input";
import truncate from "../utils/truncate";
const labelStyle = css({
color: "$mauve10",
@@ -26,8 +27,12 @@ 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";
const AccountDialog = ({
export const AccountDialog = ({
activeAccountAddress,
setActiveAccountAddress,
}: {
@@ -36,11 +41,13 @@ const AccountDialog = ({
}) => {
const snap = useSnapshot(state);
const [showSecret, setShowSecret] = useState(false);
const activeAccount = snap.accounts.find(account => account.address === activeAccountAddress);
const activeAccount = snap.accounts.find(
(account) => account.address === activeAccountAddress
);
return (
<Dialog
open={Boolean(activeAccountAddress)}
onOpenChange={open => {
onOpenChange={(open) => {
setShowSecret(false);
!open && setActiveAccountAddress(null);
}}
@@ -84,6 +91,22 @@ 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" }}>
@@ -135,7 +158,7 @@ const AccountDialog = ({
}}
ghost
size="xs"
onClick={() => setShowSecret(curr => !curr)}
onClick={() => setShowSecret((curr) => !curr)}
>
{showSecret ? "Hide" : "Show"}
</Button>
@@ -161,6 +184,8 @@ const AccountDialog = ({
<Text
css={{
fontFamily: "$monospace",
display: "flex",
alignItems: "center",
}}
>
{Dinero({
@@ -173,11 +198,26 @@ 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"
>
@@ -199,9 +239,26 @@ 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>
@@ -221,15 +278,17 @@ interface AccountProps {
showHookStats?: boolean;
}
const Accounts: FC<AccountProps> = props => {
const Accounts: FC<AccountProps> = (props) => {
const snap = useSnapshot(state);
const [activeAccountAddress, setActiveAccountAddress] = useState<string | null>(null);
const [activeAccountAddress, setActiveAccountAddress] = useState<
string | null
>(null);
useEffect(() => {
const fetchAccInfo = async () => {
if (snap.clientStatus === "online") {
const requests = snap.accounts.map(acc =>
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,
})
@@ -239,15 +298,17 @@ const Accounts: FC<AccountProps> = props => {
const address = res?.account_data?.Account as string;
const balance = res?.account_data?.Balance as string;
const sequence = res?.account_data?.Sequence as number;
const accountToUpdate = state.accounts.find(acc => acc.address === address);
const accountToUpdate = state.accounts.find(
(acc) => acc.address === address
);
if (accountToUpdate) {
accountToUpdate.xrp = balance;
accountToUpdate.sequence = sequence;
}
});
const objectRequests = snap.accounts.map(acc => {
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,
});
@@ -255,11 +316,14 @@ const Accounts: FC<AccountProps> = props => {
const objectResponses = await Promise.all(objectRequests);
objectResponses.forEach((res: any) => {
const address = res?.account as string;
const accountToUpdate = state.accounts.find(acc => acc.address === address);
const accountToUpdate = state.accounts.find(
(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) || [];
}
});
}
@@ -268,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 () => {
@@ -330,14 +394,13 @@ const Accounts: FC<AccountProps> = props => {
fontSize: "13px",
wordWrap: "break-word",
fontWeight: "$body",
fontFamily: "$monospace",
gap: 0,
height: "calc(100% - 52px)",
flexWrap: "nowrap",
overflowY: "auto",
}}
>
{snap.accounts.map(account => (
{snap.accounts.map((account) => (
<Flex
column
key={account.address + account.name}
@@ -383,28 +446,20 @@ const Accounts: FC<AccountProps> = props => {
</Text>
</Box>
{!props.hideDeployBtn && (
<Button
css={{ ml: "auto" }}
size="xs"
uppercase
isLoading={account.isLoading}
disabled={
account.isLoading ||
!snap.files.filter(file => file.compiledWatContent).length
}
variant="secondary"
onClick={e => {
<div
className="hook-deploy-button"
onClick={(e) => {
e.stopPropagation();
deployHook(account);
}}
>
Deploy
</Button>
<SetHookDialog account={account} />
</div>
)}
</Flex>
{props.showHookStats && (
<Text muted small css={{ mt: "$2" }}>
{account.hooks.length} hook{account.hooks.length === 1 ? "" : "s"} installed
{account.hooks.length} hook
{account.hooks.length === 1 ? "" : "s"} installed
</Text>
)}
</Flex>
@@ -419,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 (
@@ -436,7 +496,7 @@ const ImportAccountDialog = () => {
name="secret"
type="password"
value={value}
onChange={e => setValue(e.target.value)}
onChange={(e) => setValue(e.target.value)}
/>
</DialogDescription>

View File

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

View File

@@ -1,84 +1,145 @@
import { useEffect, useState } from "react";
import { useSnapshot } from "valtio";
import { useCallback, useEffect } from "react";
import { proxy, ref, useSnapshot } from "valtio";
import { Select } from ".";
import state from "../state";
import state, { ILog } from "../state";
import { extractJSON } from "../utils/json";
import LogBox from "./LogBox";
import Text from "./Text";
interface ISelect<T = string> {
label: string;
value: T;
}
const streamState = proxy({
selectedAccount: null as ISelect | null,
logs: [] as ILog[],
socket: undefined as WebSocket | undefined,
});
const DebugStream = () => {
const snap = useSnapshot(state);
const { selectedAccount, logs, socket } = useSnapshot(streamState);
const { accounts } = useSnapshot(state);
const accountOptions = snap.accounts.map(acc => ({
const accountOptions = accounts.map((acc) => ({
label: acc.name,
value: acc.address,
}));
const [selectedAccount, setSelectedAccount] = useState<typeof accountOptions[0] | null>(null);
const renderNav = () => (
<>
<Text css={{ mx: "$2", fontSize: "inherit" }}>Account: </Text>
<Select
instanceId="debugStreamAccount"
instanceId="DSAccount"
placeholder="Select account"
options={accountOptions}
hideSelectedOptions
value={selectedAccount}
onChange={acc => setSelectedAccount(acc as any)}
css={{ width: "30%" }}
onChange={(acc) => (streamState.selectedAccount = acc as any)}
css={{ width: "100%" }}
/>
</>
);
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) {
return;
if (account && (!socket || !socket.url.endsWith(account))) {
socket?.close();
streamState.socket = ref(
new WebSocket(
`wss://${process.env.NEXT_PUBLIC_DEBUG_STREAM_URL}/${account}`
)
);
} else if (!account && socket) {
socket.close();
streamState.socket = undefined;
}
const socket = new WebSocket(`wss://hooks-testnet-debugstream.xrpl-labs.com/${account}`);
}, [selectedAccount?.value, socket]);
useEffect(() => {
const account = selectedAccount?.value;
const socket = streamState.socket;
if (!socket) return;
const onOpen = () => {
state.debugLogs = [];
state.debugLogs.push({
streamState.logs = [];
streamState.logs.push({
type: "success",
message: `Debug stream opened for account ${account}`,
});
};
const onError = () => {
state.debugLogs.push({
streamState.logs.push({
type: "error",
message: "Something went wrong in establishing connection!",
message: "Something went wrong! Check your connection and try again.",
});
setSelectedAccount(null);
};
const onClose = (e: CloseEvent) => {
streamState.logs.push({
type: "error",
message: `Connection was closed. [code: ${e.code}]`,
});
streamState.selectedAccount = null;
};
const onMessage = (event: any) => {
if (!event.data) return;
state.debugLogs.push({
type: "log",
message: event.data,
});
const log = prepareLog(event.data);
// Filter out account_info and account_objects requests
try {
const parsed = JSON.parse(log.jsonData);
if (parsed?.id?._Request?.includes("hooks-builder-req")) {
return;
}
} catch (err) {
// Lets just skip if we cannot parse the message
}
return streamState.logs.push(log);
};
socket.addEventListener("open", onOpen);
socket.addEventListener("close", onError);
socket.addEventListener("close", onClose);
socket.addEventListener("error", onError);
socket.addEventListener("message", onMessage);
return () => {
socket.removeEventListener("open", onOpen);
socket.removeEventListener("close", onError);
socket.removeEventListener("close", onClose);
socket.removeEventListener("message", onMessage);
socket.close();
socket.removeEventListener("error", onError);
};
}, [selectedAccount]);
}, [prepareLog, selectedAccount?.value, socket]);
return (
<LogBox
enhanced
renderNav={renderNav}
title="Debug stream"
logs={snap.debugLogs}
clearLog={() => (state.debugLogs = [])}
logs={logs}
clearLog={() => (streamState.logs = [])}
/>
);
};

View File

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

View File

@@ -59,8 +59,6 @@ import {
} from "./AlertDialog";
import { styled } from "../stitches.config";
const DEFAULT_EXTENSION = ".c";
const ErrorText = styled(Text, {
color: "$error",
mt: "$1",
@@ -91,33 +89,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]);

View File

@@ -67,7 +67,6 @@ const setMarkers = (monacoE: typeof monaco) => {
// exact same range (location) where the markers are
const models = monacoE.editor.getModels();
models.forEach((model) => {
console.log(decorations);
decorations[model.id] = model?.deltaDecorations(
decorations?.[model.id] || [],
markers
@@ -100,7 +99,6 @@ const setMarkers = (monacoE: typeof monaco) => {
}))
);
});
console.log("decorat", decorations);
};
const HooksEditor = () => {
@@ -138,7 +136,6 @@ const HooksEditor = () => {
}}
>
<EditorNavigation />
{console.log(snap)}
{snap.files.length > 0 && router.isReady ? (
<Editor
className="hooks-editor"
@@ -148,7 +145,6 @@ const HooksEditor = () => {
path={`file:///work/c/${snap.files?.[snap.active]?.name}`}
defaultValue={snap.files?.[snap.active]?.content}
beforeMount={(monaco) => {
console.log(monaco.languages.getLanguages());
if (!snap.editorCtx) {
snap.files.forEach((file) =>
monaco.editor.createModel(

View File

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

View File

@@ -3,6 +3,14 @@ import { styled } from "../stitches.config";
const StyledLink = styled("a", {
color: "CurrentColor",
textDecoration: "underline",
cursor: 'pointer',
variants: {
highlighted: {
true: {
color: '$blue9'
}
}
}
});
export default StyledLink;

View File

@@ -1,17 +1,22 @@
import React, { useRef, useLayoutEffect, ReactNode } 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";
import Container from "./Container";
import Box from "./Box";
import Flex from "./Flex";
import LogText from "./LogText";
import { ILog } from "../state";
import Text from "./Text";
import Button from "./Button";
import Heading from "./Heading";
import Link from "./Link";
import state, { ILog } from "../state";
import { Pre, Link, Heading, Button, Text, Flex, Box } from ".";
import regexifyString from "regexify-string";
import { useSnapshot } from "valtio";
import { AccountDialog } from "./Accounts";
interface ILogBox {
title: string;
@@ -21,7 +26,7 @@ interface ILogBox {
enhanced?: boolean;
}
const LogBox: React.FC<ILogBox> = ({
const LogBox: FC<ILogBox> = ({
title,
clearLog,
logs,
@@ -55,6 +60,7 @@ const LogBox: React.FC<ILogBox> = ({
}}
>
<Flex
fluid
css={{
height: "48px",
alignItems: "center",
@@ -78,7 +84,15 @@ const LogBox: React.FC<ILogBox> = ({
>
<Notepad size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
</Heading>
{renderNav?.()}
<Flex
row
align="center"
css={{
width: "50%", // TODO make it max without breaking layout!
}}
>
{renderNav?.()}
</Flex>
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
{clearLog && (
<Button ghost size="xs" onClick={clearLog}>
@@ -117,17 +131,11 @@ const LogBox: React.FC<ILogBox> = ({
backgroundColor: enhanced ? "$backgroundAlt" : undefined,
},
},
p: enhanced ? "$2 $1" : undefined,
p: enhanced ? "$1" : undefined,
my: enhanced ? "$1" : undefined,
}}
>
<LogText variant={log.type}>
{log.message}{" "}
{log.link && (
<NextLink href={log.link} shallow passHref>
<Link as="a">{log.linkText}</Link>
</NextLink>
)}
</LogText>
<Log {...log} />
</Box>
))}
{children}
@@ -137,4 +145,78 @@ const LogBox: React.FC<ILogBox> = ({
);
};
export const Log: FC<ILog> = ({
type,
timestamp: timestamp,
message: _message,
link,
linkText,
defaultCollapsed,
jsonData: _jsonData,
}) => {
const [expanded, setExpanded] = useState(!defaultCollapsed);
const { accounts } = useSnapshot(state);
const [dialogAccount, setDialogAccount] = useState<string | null>(null);
const enrichAccounts = useCallback(
(str?: string): ReactNode => {
if (!str || !accounts.length) return null;
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;
return (
<Link
key={match + idx}
as="a"
onClick={() => setDialogAccount(match)}
title={match}
highlighted
>
{name || match}
</Link>
);
},
input: str,
});
return <>{res}</>;
},
[accounts]
);
_message = _message.trim().replace(/\n /gi, "\n");
const message = enrichAccounts(_message);
const jsonData = enrichAccounts(_jsonData);
return (
<>
<AccountDialog
setActiveAccountAddress={setDialogAccount}
activeAccountAddress={dialogAccount}
/>
<LogText variant={type}>
{timestamp && (
<Text muted monospace>
{timestamp}{" "}
</Text>
)}
<Pre>{message} </Pre>
{link && (
<NextLink href={link} shallow passHref>
<Link as="a">{linkText}</Link>
</NextLink>
)}
{jsonData && (
<Link onClick={() => setExpanded(!expanded)} as="a">
{expanded ? "Collapse" : "Expand"}
</Link>
)}
{expanded && jsonData && <Pre block>{jsonData}</Pre>}
</LogText>
</>
);
};
export default LogBox;

View File

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

27
components/Pre.tsx Normal file
View File

@@ -0,0 +1,27 @@
import { styled } from "../stitches.config";
const Pre = styled("span", {
m: 0,
wordBreak: "break-all",
fontFamily: '$monospace',
whiteSpace: 'pre-wrap',
variants: {
fluid: {
true: {
width: "100%",
},
},
line: {
true: {
whiteSpace: 'pre-line'
}
},
block: {
true: {
display: 'block'
}
}
},
});
export default Pre;

View File

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

View File

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

View File

@@ -14,6 +14,11 @@ const Text = styled("span", {
true: {
color: '$mauve9'
}
},
monospace: {
true: {
fontFamily: '$monospace'
}
}
}
});

View File

@@ -10,6 +10,7 @@ export * from "./Tabs";
export * from "./AlertDialog";
export { default as Box } from "./Box";
export { default as Button } from "./Button";
export { default as Pre } from "./Pre";
export { default as ButtonGroup } from "./ButtonGroup";
export { default as DeployFooter } from "./DeployFooter";
export * from "./Dialog";

1
next-env.d.ts vendored
View File

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

View File

@@ -27,6 +27,7 @@
"javascript-time-ago": "^2.3.11",
"jszip": "^3.7.1",
"lodash.uniqby": "^4.7.0",
"lodash.xor": "^4.5.0",
"monaco-editor": "^0.30.1",
"next": "^12.0.4",
"next-auth": "^4.0.0-beta.5",
@@ -40,6 +41,7 @@
"re-resizable": "^6.9.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-hook-form": "^7.28.0",
"react-hot-keys": "^2.7.1",
"react-hot-toast": "^2.1.1",
"react-new-window": "^0.2.1",
@@ -48,17 +50,19 @@
"react-stay-scrolled": "^7.4.0",
"react-time-ago": "^7.1.9",
"reconnecting-websocket": "^4.4.0",
"regexify-string": "^1.0.17",
"valtio": "^1.2.5",
"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",

View File

@@ -15,7 +15,10 @@ import state from "../state";
import TimeAgo from "javascript-time-ago";
import en from "javascript-time-ago/locale/en.json";
TimeAgo.addDefaultLocale(en);
import { useSnapshot } from "valtio";
TimeAgo.setDefaultLocale(en.locale);
TimeAgo.addLocale(en)
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
const router = useRouter();
@@ -25,15 +28,29 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
const origin = "https://xrpl-hooks-ide.vercel.app"; // TODO: Change when site is deployed
const shareImg = "/share-image.png";
const snap = useSnapshot(state);
useEffect(() => {
if (gistId && router.isReady) {
fetchFiles(gistId);
} else {
if (!gistId && router.isReady && !router.pathname.includes("/sign-in")) {
if (
!gistId &&
router.isReady &&
!router.pathname.includes("/sign-in") &&
!snap.files.length &&
!snap.mainModalShowed
) {
state.mainModalOpen = true;
state.mainModalShowed = true;
}
}
}, [gistId, router.isReady, router.pathname]);
}, [
gistId,
router.isReady,
router.pathname,
snap.files,
snap.mainModalShowed,
]);
return (
<>
@@ -95,16 +112,6 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
content="#FDFCFD"
media="(prefers-color-scheme: light)"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin=""
/>
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital@0;1&family=Work+Sans:wght@400;600;700&display=swap"
rel="stylesheet"
/>
</Head>
<IdProvider>
<SessionProvider session={session}>

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,9 @@
import React from "react";
import dynamic from "next/dynamic";
import React from "react";
import Split from "react-split";
import { useSnapshot } from "valtio";
import state from "../../state";
import Split from "react-split";
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
const DeployEditor = dynamic(() => import("../../components/DeployEditor"), {
ssr: false,
@@ -17,21 +18,22 @@ const LogBox = dynamic(() => import("../../components/LogBox"), {
});
const Deploy = () => {
const snap = useSnapshot(state);
const { deployLogs } = useSnapshot(state);
return (
<Split
direction="vertical"
gutterSize={4}
gutterAlign="center"
sizes={[40, 60]}
sizes={getSplit("deployVertical") || [40, 60]}
style={{ height: "calc(100vh - 60px)" }}
onDragEnd={(e) => saveSplit("deployVertical", e)}
>
<main style={{ display: "flex", flex: 1, position: "relative" }}>
<DeployEditor />
</main>
<Split
direction="horizontal"
sizes={[50, 50]}
sizes={getSplit("deployHorizontal") || [50, 50]}
minSize={[320, 160]}
gutterSize={4}
gutterAlign="center"
@@ -41,6 +43,7 @@ const Deploy = () => {
width: "100%",
height: "100%",
}}
onDragEnd={(e) => saveSplit("deployHorizontal", e)}
>
<div style={{ alignItems: "stretch", display: "flex" }}>
<Accounts />
@@ -48,7 +51,7 @@ const Deploy = () => {
<div>
<LogBox
title="Deploy Log"
logs={snap.deployLogs}
logs={deployLogs}
clearLog={() => (state.deployLogs = [])}
/>
</div>

View File

@@ -1,14 +1,15 @@
import dynamic from "next/dynamic";
import { useSnapshot } from "valtio";
import Hotkeys from "react-hot-keys";
import { Play } from "phosphor-react";
import Split from "react-split";
import type { NextPage } from "next";
import { compileCode } from "../../state/actions";
import state from "../../state";
import Button from "../../components/Button";
import dynamic from "next/dynamic";
import { Play } from "phosphor-react";
import Hotkeys from "react-hot-keys";
import Split from "react-split";
import { useSnapshot } from "valtio";
import Box from "../../components/Box";
import Button from "../../components/Button";
import state from "../../state";
import { compileCode } from "../../state/actions";
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
const HooksEditor = dynamic(() => import("../../components/HooksEditor"), {
ssr: false,
@@ -24,11 +25,12 @@ const Home: NextPage = () => {
return (
<Split
direction="vertical"
sizes={[70, 30]}
sizes={getSplit("developVertical") || [70, 30]}
minSize={[100, 100]}
gutterAlign="center"
gutterSize={4}
style={{ height: "calc(100vh - 60px)" }}
onDragEnd={(e) => saveSplit("developVertical", e)}
>
<main style={{ display: "flex", flex: 1, position: "relative" }}>
<HooksEditor />

View File

@@ -1,22 +1,23 @@
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,
Box,
Tabs,
Tab,
Input,
Select,
Tab,
Tabs,
Text,
Button,
} from "../../components";
import { Play } from "phosphor-react";
import dynamic from "next/dynamic";
import { useSnapshot } from "valtio";
import Split from "react-split";
import transactionsData from "../../content/transactions.json";
import state from "../../state";
import { sendTransaction } from "../../state/actions";
import { useCallback, useEffect, useState, FC } from "react";
import transactionsData from "../../content/transactions.json";
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
const DebugStream = dynamic(() => import("../../components/DebugStream"), {
ssr: false,
@@ -188,13 +189,23 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
return (
<Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
<Container
css={{ p: "$3 0", fontSize: "$sm", height: "calc(100% - 28px)" }}
css={{
p: "$3 01",
fontSize: "$sm",
height: "calc(100% - 45px)",
}}
>
<Flex column fluid css={{ height: "100%", overflowY: "auto" }}>
<Flex
row
fluid
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
mt: "1px",
pr: "1px",
}}
>
<Text muted css={{ mr: "$3" }}>
Transaction type:{" "}
@@ -212,7 +223,12 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
<Flex
row
fluid
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
pr: "1px",
}}
>
<Text muted css={{ mr: "$3" }}>
Account:{" "}
@@ -234,6 +250,7 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
pr: "1px",
}}
>
<Text muted css={{ mr: "$3" }}>
@@ -247,7 +264,6 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
Amount: { type: "currency", value: e.target.value },
})
}
variant="deep"
css={{ width: "70%", flex: "inherit", height: "$9" }}
/>
</Flex>
@@ -260,6 +276,7 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
pr: "1px",
}}
>
<Text muted css={{ mr: "$3" }}>
@@ -294,6 +311,7 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
pr: "1px",
}}
>
<Text muted css={{ mr: "$3" }}>
@@ -310,7 +328,6 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
: e.target.value,
})
}
variant="deep"
css={{ width: "70%", flex: "inherit", height: "$9" }}
/>
</Flex>
@@ -349,16 +366,17 @@ const Transaction: FC<Props> = ({ header, ...props }) => {
};
const Test = () => {
const snap = useSnapshot(state);
const { transactionLogs } = useSnapshot(state);
const [tabHeaders, setTabHeaders] = useState<string[]>(["test1.json"]);
return (
<Container css={{ px: 0 }}>
<Split
direction="vertical"
sizes={[50, 50]}
sizes={getSplit("testVertical") || [50, 50]}
gutterSize={4}
gutterAlign="center"
style={{ height: "calc(100vh - 60px)" }}
onDragEnd={(e) => saveSplit("testVertical", e)}
>
<Flex
row
@@ -370,7 +388,7 @@ const Test = () => {
>
<Split
direction="horizontal"
sizes={[50, 50]}
sizes={getSplit("testHorizontal") || [50, 50]}
minSize={[180, 320]}
gutterSize={4}
gutterAlign="center"
@@ -380,6 +398,7 @@ const Test = () => {
width: "100%",
height: "100%",
}}
onDragEnd={(e) => saveSplit("testHorizontal", e)}
>
<Box css={{ width: "55%", px: "$2" }}>
<Tabs
@@ -428,7 +447,7 @@ const Test = () => {
>
<LogBox
title="Development Log"
logs={snap.transactionLogs}
logs={transactionLogs}
clearLog={() => (state.transactionLogs = [])}
/>
</Box>

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -29,8 +29,8 @@ export const names = [
*/
export const addFaucetAccount = async (showToast: boolean = false) => {
// Lets limit the number of faucet accounts to 5 for now
if (state.accounts.length > 4) {
return toast.error("You can only have maximum 5 accounts");
if (state.accounts.length > 5) {
return toast.error("You can only have maximum 6 accounts");
}
if (typeof window !== 'undefined') {
@@ -50,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();
}
}
}

View File

@@ -1,6 +1,27 @@
import { derive, sign } from "xrpl-accountlib";
import toast from "react-hot-toast";
import state, { IAccount } from "../index";
import calculateHookOn, { TTS } from "../../utils/hookOnCalculator";
import { SetHookData } from "../../components/SetHookDialog";
const hash = async (string: string) => {
const utf8 = new TextEncoder().encode(string);
const hashBuffer = await crypto.subtle.digest('SHA-256', utf8);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray
.map((bytes) => bytes.toString(16).padStart(2, '0'))
.join('');
return hashHex;
}
function 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) {
@@ -30,7 +51,7 @@ function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
* hex string, signs the transaction and deploys it to
* Hooks testnet.
*/
export const deployHook = async (account: IAccount & { name?: string }) => {
export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData) => {
if (
!state.files ||
state.files.length === 0 ||
@@ -45,17 +66,45 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
if (!state.client) {
return;
}
const HookNamespace = await hash(arrayBufferToHex(
state.files?.[state.active]?.compiledContent
).toUpperCase());
const hookOnValues: (keyof TTS)[] = data.Invoke.map(tt => tt.value);
const { HookParameters } = data;
const filteredHookParameters = HookParameters.filter(hp => hp.HookParameter.HookParameterName && hp.HookParameter.HookParameterValue)?.map(aa => ({ HookParameter: { HookParameterName: toHex(aa.HookParameter.HookParameterName || ''), HookParameterValue: toHex(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",
CreateCode: arrayBufferToHex(
state.files?.[state.active]?.compiledContent
).toUpperCase(),
HookOn: "0000000000000000",
Sequence: account.sequence,
Fee: "1000",
Fee: "100000",
Hooks: [
{
Hook: {
CreateCode: arrayBufferToHex(
state.files?.[state.active]?.compiledContent
).toUpperCase(),
HookOn: calculateHookOn(hookOnValues),
HookNamespace,
HookApiVersion: 0,
Flags: 1,
// ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
...(filteredHookParameters.length > 0 && { HookParameters: filteredHookParameters }),
}
}
]
};
const keypair = derive.familySeed(account.secret);
const { signedTransaction } = sign(tx, keypair);
const currentAccount = state.accounts.find(
@@ -64,11 +113,13 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
if (currentAccount) {
currentAccount.isLoading = true;
}
let submitRes;
try {
const submitRes = await state.client.send({
submitRes = await state.client.send({
command: "submit",
tx_blob: signedTransaction,
});
if (submitRes.engine_result === "tesSUCCESS") {
state.deployLogs.push({
type: "success",
@@ -81,7 +132,7 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
} else {
state.deployLogs.push({
type: "error",
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message}`,
message: `[${submitRes.engine_result || submitRes.error}] ${submitRes.engine_result_message || submitRes.error_exception}`,
});
}
} catch (err) {
@@ -94,5 +145,79 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
if (currentAccount) {
currentAccount.isLoading = false;
}
return submitRes;
}
};
export const deleteHook = async (account: IAccount & { name?: string }) => {
if (!state.client) {
return;
}
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;
}
};

View File

@@ -24,6 +24,7 @@ export const importAccount = (secret: string) => {
sequence: 1,
hooks: [],
isLoading: false,
version: '2'
});
return toast.success("Account imported successfully!");
};

View File

@@ -0,0 +1,15 @@
import { snapshot } from "valtio"
import state from ".."
export type SplitSize = number[]
export const saveSplit = (splitId: string, event: SplitSize) => {
state.splits[splitId] = event
}
export const getSplit = (splitId: string): SplitSize | null => {
const { splits } = snapshot(state)
const split = splits[splitId]
return split ? split : null
}

View File

@@ -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);

View File

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

View File

@@ -1,7 +1,13 @@
import { proxy, ref, subscribe } from "valtio";
import { devtools } from 'valtio/utils'
import type monaco from "monaco-editor";
import { proxy, ref, subscribe } from "valtio";
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;
@@ -28,13 +34,17 @@ export interface IAccount {
sequence: number;
hooks: string[];
isLoading: boolean;
version?: string;
}
export interface ILog {
type: "error" | "warning" | "log" | "success";
message: string;
jsonData?: any,
timestamp?: string;
link?: string;
linkText?: string;
defaultCollapsed?: boolean
}
export interface IState {
@@ -51,14 +61,17 @@ export interface IState {
logs: ILog[];
deployLogs: ILog[];
transactionLogs: ILog[];
debugLogs: ILog[];
editorCtx?: typeof monaco.editor;
editorSettings: {
tabSize: number;
};
splits: {
[id: string]: SplitSize
};
client: XrplClient | null;
clientStatus: "offline" | "online";
mainModalOpen: boolean;
mainModalShowed: boolean;
accounts: IAccount[];
}
@@ -74,7 +87,6 @@ let initialState: IState = {
logs: [],
deployLogs: [],
transactionLogs: [],
debugLogs: [],
editorCtx: undefined,
gistId: undefined,
gistOwner: undefined,
@@ -84,14 +96,19 @@ let initialState: IState = {
editorSettings: {
tabSize: 2,
},
splits: {},
client: null,
clientStatus: "offline" as "offline",
mainModalOpen: false,
mainModalShowed: false,
accounts: [],
};
let localStorageAccounts: string | null = null;
let initialAccounts: IAccount[] = [];
// TODO: What exactly should we store in localStorage? editorSettings, splits, accounts?
// Check if there's a persited accounts in localStorage
if (typeof window !== "undefined") {
try {
@@ -103,6 +120,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
@@ -111,9 +130,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);
@@ -140,4 +158,4 @@ if (typeof window !== "undefined") {
}
});
}
export default state
export default state

View File

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

41
utils/hookOnCalculator.ts Normal file
View File

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

21
utils/json.ts Normal file
View File

@@ -0,0 +1,21 @@
export const extractJSON = (str?: string) => {
if (!str) return
let firstOpen = 0, firstClose = 0, candidate = '';
firstOpen = str.indexOf('{', firstOpen + 1);
do {
firstClose = str.lastIndexOf('}');
if (firstClose <= firstOpen) {
return;
}
do {
candidate = str.substring(firstOpen, firstClose + 1);
try {
let result = JSON.parse(candidate);
return { result, start: firstOpen < 0 ? 0 : firstOpen, end: firstClose }
}
catch (e) { }
firstClose = str.substring(0, firstClose).lastIndexOf('}');
} while (firstClose > firstOpen);
firstOpen = str.indexOf('{', firstOpen + 1);
} while (firstOpen != -1);
}

View File

@@ -25,6 +25,8 @@ 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 hooksReserveLimit from "./md/hooks-reserve-limit.md";
@@ -69,6 +71,8 @@ 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-reserve-limit": hooksReserveLimit,

View File

@@ -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).

View File

@@ -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).

View File

@@ -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).

View File

@@ -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)

View File

@@ -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).

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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).

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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).

View File

@@ -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)

View 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)

View 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)

View File

@@ -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).

View File

@@ -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).

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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).

View File

@@ -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)

View File

@@ -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)

View File

@@ -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).

2182
yarn.lock

File diff suppressed because it is too large Load Diff