update set hook functionality
This commit is contained in:
245
components/SetHookDialog.tsx
Normal file
245
components/SetHookDialog.tsx
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
import { Plus, Trash, X } from "phosphor-react";
|
||||||
|
import React from "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";
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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> = (data) => {
|
||||||
|
deployHook(account, data);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
ghost
|
||||||
|
size="xs"
|
||||||
|
uppercase
|
||||||
|
variant={"secondary"}
|
||||||
|
disabled={
|
||||||
|
account.isLoading ||
|
||||||
|
!snap.files.filter((file) => file.compiledWatContent).length
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Set Hook
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<DialogContent css={{ overflowY: "auto" }}>
|
||||||
|
<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="absolute"
|
||||||
|
styles={{
|
||||||
|
menuPortal: (provided) => ({
|
||||||
|
...provided,
|
||||||
|
zIndex: 9000,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
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">
|
||||||
|
Set Hook
|
||||||
|
</Button>
|
||||||
|
{/* </DialogClose> */}
|
||||||
|
</Flex>
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
|
||||||
|
<X size="20px" />
|
||||||
|
</Box>
|
||||||
|
</DialogClose>
|
||||||
|
</DialogContent>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SetHookDialog;
|
||||||
@@ -1,7 +1,19 @@
|
|||||||
import { derive, sign } from "xrpl-accountlib";
|
import { derive, sign } from "xrpl-accountlib";
|
||||||
|
|
||||||
import state, { IAccount } from "../index";
|
import state, { IAccount } from "../index";
|
||||||
import calculateHookOn from "../../utils/hookOnCalculator";
|
import calculateHookOn, { TTS } from "../../utils/hookOnCalculator";
|
||||||
|
import { SetHookData } from "../../components/SetHookDialog";
|
||||||
|
|
||||||
|
const hash = async (string: string) => {
|
||||||
|
const utf8 = new TextEncoder().encode(string);
|
||||||
|
const hashBuffer = await crypto.subtle.digest('SHA-256', utf8);
|
||||||
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||||
|
const hashHex = hashArray
|
||||||
|
.map((bytes) => bytes.toString(16).padStart(2, '0'))
|
||||||
|
.join('');
|
||||||
|
return hashHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
|
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
|
||||||
if (!arrayBuffer) {
|
if (!arrayBuffer) {
|
||||||
@@ -31,7 +43,7 @@ function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
|
|||||||
* hex string, signs the transaction and deploys it to
|
* hex string, signs the transaction and deploys it to
|
||||||
* Hooks testnet.
|
* Hooks testnet.
|
||||||
*/
|
*/
|
||||||
export const deployHook = async (account: IAccount & { name?: string }) => {
|
export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData) => {
|
||||||
if (
|
if (
|
||||||
!state.files ||
|
!state.files ||
|
||||||
state.files.length === 0 ||
|
state.files.length === 0 ||
|
||||||
@@ -46,6 +58,21 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
|
|||||||
if (!state.client) {
|
if (!state.client) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const HookNamespace = await hash(arrayBufferToHex(
|
||||||
|
state.files?.[state.active]?.compiledContent
|
||||||
|
).toUpperCase());
|
||||||
|
const hookOnValues: (keyof TTS)[] = data.Invoke.map(tt => tt.value);
|
||||||
|
const { HookParameters } = data;
|
||||||
|
const filteredHookParameters = HookParameters.filter(hp => hp.HookParameter.HookParameterName && hp.HookParameter.HookParameterValue);
|
||||||
|
// const filteredHookGrants = HookGrants.filter(hg => hg.HookGrant.Authorize || hg.HookGrant.HookHash).map(hg => {
|
||||||
|
// return {
|
||||||
|
// HookGrant: {
|
||||||
|
// ...(hg.HookGrant.Authorize && { Authorize: hg.HookGrant.Authorize }),
|
||||||
|
// // HookHash: hg.HookGrant.HookHash || undefined
|
||||||
|
// ...(hg.HookGrant.HookHash && { HookHash: hg.HookGrant.HookHash })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
const tx = {
|
const tx = {
|
||||||
Account: account.address,
|
Account: account.address,
|
||||||
@@ -53,25 +80,17 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
|
|||||||
Sequence: account.sequence,
|
Sequence: account.sequence,
|
||||||
Fee: "100000",
|
Fee: "100000",
|
||||||
Hooks: [
|
Hooks: [
|
||||||
// {
|
|
||||||
// Hook: {
|
|
||||||
// CreateCode:
|
|
||||||
// HookApiVersion: 0,
|
|
||||||
// HookNamespace: "Kissa",
|
|
||||||
// HookOn: calculateHookOn([]),
|
|
||||||
// Flags: 2
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// [
|
|
||||||
{
|
{
|
||||||
Hook: {
|
Hook: {
|
||||||
CreateCode: arrayBufferToHex(
|
CreateCode: arrayBufferToHex(
|
||||||
state.files?.[state.active]?.compiledContent
|
state.files?.[state.active]?.compiledContent
|
||||||
).toUpperCase(),
|
).toUpperCase(),
|
||||||
HookOn: calculateHookOn([]),
|
HookOn: calculateHookOn(hookOnValues),
|
||||||
HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
|
HookNamespace,
|
||||||
HookApiVersion: 0,
|
HookApiVersion: 0,
|
||||||
|
Flags: 1,
|
||||||
|
// ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
|
||||||
|
...(filteredHookParameters.length > 0 && { HookParameters: filteredHookParameters }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user