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 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) {
 | 
			
		||||
  if (!arrayBuffer) {
 | 
			
		||||
@@ -31,7 +43,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 ||
 | 
			
		||||
@@ -46,6 +58,21 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
 | 
			
		||||
  if (!state.client) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  const HookNamespace = await hash(arrayBufferToHex(
 | 
			
		||||
    state.files?.[state.active]?.compiledContent
 | 
			
		||||
  ).toUpperCase());
 | 
			
		||||
  const hookOnValues: (keyof TTS)[] = data.Invoke.map(tt => tt.value);
 | 
			
		||||
  const { HookParameters } = data;
 | 
			
		||||
  const filteredHookParameters = HookParameters.filter(hp => hp.HookParameter.HookParameterName && hp.HookParameter.HookParameterValue);
 | 
			
		||||
  // const filteredHookGrants = HookGrants.filter(hg => hg.HookGrant.Authorize || hg.HookGrant.HookHash).map(hg => {
 | 
			
		||||
  //   return {
 | 
			
		||||
  //     HookGrant: {
 | 
			
		||||
  //       ...(hg.HookGrant.Authorize && { Authorize: hg.HookGrant.Authorize }),
 | 
			
		||||
  //       // HookHash: hg.HookGrant.HookHash || undefined
 | 
			
		||||
  //       ...(hg.HookGrant.HookHash && { HookHash: hg.HookGrant.HookHash })
 | 
			
		||||
  //     }
 | 
			
		||||
  //   }
 | 
			
		||||
  // });
 | 
			
		||||
  if (typeof window !== "undefined") {
 | 
			
		||||
    const tx = {
 | 
			
		||||
      Account: account.address,
 | 
			
		||||
@@ -53,25 +80,17 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
 | 
			
		||||
      Sequence: account.sequence,
 | 
			
		||||
      Fee: "100000",
 | 
			
		||||
      Hooks: [
 | 
			
		||||
        //   {
 | 
			
		||||
        //     Hook: {
 | 
			
		||||
        //       CreateCode: 
 | 
			
		||||
        //       HookApiVersion: 0,
 | 
			
		||||
        //       HookNamespace: "Kissa",
 | 
			
		||||
        //       HookOn: calculateHookOn([]),
 | 
			
		||||
        //       Flags: 2
 | 
			
		||||
        //     }
 | 
			
		||||
        //   }
 | 
			
		||||
        // ]
 | 
			
		||||
        // [  
 | 
			
		||||
        {
 | 
			
		||||
          Hook: {
 | 
			
		||||
            CreateCode: arrayBufferToHex(
 | 
			
		||||
              state.files?.[state.active]?.compiledContent
 | 
			
		||||
            ).toUpperCase(),
 | 
			
		||||
            HookOn: calculateHookOn([]),
 | 
			
		||||
            HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
 | 
			
		||||
            HookOn: calculateHookOn(hookOnValues),
 | 
			
		||||
            HookNamespace,
 | 
			
		||||
            HookApiVersion: 0,
 | 
			
		||||
            Flags: 1,
 | 
			
		||||
            // ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
 | 
			
		||||
            ...(filteredHookParameters.length > 0 && { HookParameters: filteredHookParameters }),
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user