Compare commits
14 Commits
feat/tx_ha
...
feat/fee-h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2dc49754f | ||
|
|
6f636645f7 | ||
|
|
377c963c7a | ||
|
|
ae038f17ff | ||
|
|
da9986eb66 | ||
|
|
a21350770e | ||
|
|
49dfd43220 | ||
|
|
4472957f5c | ||
|
|
ca46707bb5 | ||
|
|
9a6ef2c393 | ||
|
|
56203ce9c6 | ||
|
|
933bdb5968 | ||
|
|
864711697b | ||
|
|
e5eaf09721 |
@@ -469,7 +469,7 @@ const Accounts: FC<AccountProps> = (props) => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SetHookDialog account={account} />
|
<SetHookDialog accountAddress={account.address} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ import {
|
|||||||
|
|
||||||
import { TTS, tts } from "../utils/hookOnCalculator";
|
import { TTS, tts } from "../utils/hookOnCalculator";
|
||||||
import { deployHook } from "../state/actions";
|
import { deployHook } from "../state/actions";
|
||||||
import type { IAccount } from "../state";
|
|
||||||
import { useSnapshot } from "valtio";
|
import { useSnapshot } from "valtio";
|
||||||
import state from "../state";
|
import state from "../state";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { sha256 } from "../state/actions/deployHook";
|
import { prepareDeployHookTx, sha256 } from "../state/actions/deployHook";
|
||||||
|
import estimateFee from "../utils/estimateFee";
|
||||||
|
|
||||||
const transactionOptions = Object.keys(tts).map((key) => ({
|
const transactionOptions = Object.keys(tts).map((key) => ({
|
||||||
label: key,
|
label: key,
|
||||||
@@ -37,6 +37,7 @@ export type SetHookData = {
|
|||||||
value: keyof TTS;
|
value: keyof TTS;
|
||||||
label: string;
|
label: string;
|
||||||
}[];
|
}[];
|
||||||
|
Fee: string;
|
||||||
HookNamespace: string;
|
HookNamespace: string;
|
||||||
HookParameters: {
|
HookParameters: {
|
||||||
HookParameter: {
|
HookParameter: {
|
||||||
@@ -52,176 +53,266 @@ export type SetHookData = {
|
|||||||
// }[];
|
// }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SetHookDialog: React.FC<{ account: IAccount }> = ({ account }) => {
|
export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
|
||||||
const snap = useSnapshot(state);
|
({ accountAddress }) => {
|
||||||
const [isSetHookDialogOpen, setIsSetHookDialogOpen] = useState(false);
|
const snap = useSnapshot(state);
|
||||||
const {
|
const account = snap.accounts.find((acc) => acc.address === accountAddress);
|
||||||
register,
|
|
||||||
handleSubmit,
|
|
||||||
control,
|
|
||||||
watch,
|
|
||||||
setValue,
|
|
||||||
formState: { errors },
|
|
||||||
} = useForm<SetHookData>({
|
|
||||||
defaultValues: {
|
|
||||||
HookNamespace: snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const { fields, append, remove } = useFieldArray({
|
|
||||||
control,
|
|
||||||
name: "HookParameters", // unique name for your Field Array
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update value if activeWat changes
|
const [isSetHookDialogOpen, setIsSetHookDialogOpen] = useState(false);
|
||||||
useEffect(() => {
|
const {
|
||||||
setValue(
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
watch,
|
||||||
|
setValue,
|
||||||
|
getValues,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<SetHookData>({
|
||||||
|
defaultValues: {
|
||||||
|
HookNamespace:
|
||||||
|
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || "",
|
||||||
|
Invoke: transactionOptions.filter((to) => to.label === "ttPAYMENT"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { fields, append, remove } = useFieldArray({
|
||||||
|
control,
|
||||||
|
name: "HookParameters", // unique name for your Field Array
|
||||||
|
});
|
||||||
|
const [formInitialized, setFormInitialized] = useState(false);
|
||||||
|
const [estimateLoading, setEstimateLoading] = useState(false);
|
||||||
|
|
||||||
|
// Update value if activeWat changes
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(
|
||||||
|
"HookNamespace",
|
||||||
|
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || ""
|
||||||
|
);
|
||||||
|
setFormInitialized(true);
|
||||||
|
}, [snap.activeWat, snap.files, setValue]);
|
||||||
|
// const {
|
||||||
|
// fields: grantFields,
|
||||||
|
// append: grantAppend,
|
||||||
|
// remove: grantRemove,
|
||||||
|
// } = useFieldArray({
|
||||||
|
// control,
|
||||||
|
// name: "HookGrants", // unique name for your Field Array
|
||||||
|
// });
|
||||||
|
const [hashedNamespace, setHashedNamespace] = useState("");
|
||||||
|
const namespace = watch(
|
||||||
"HookNamespace",
|
"HookNamespace",
|
||||||
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || ""
|
snap.files?.[snap.active]?.name?.split(".")?.[0] || ""
|
||||||
);
|
);
|
||||||
}, [snap.activeWat, snap.files, setValue]);
|
const calculateHashedValue = useCallback(async () => {
|
||||||
// const {
|
const hashedVal = await sha256(namespace);
|
||||||
// fields: grantFields,
|
setHashedNamespace(hashedVal.toUpperCase());
|
||||||
// append: grantAppend,
|
}, [namespace]);
|
||||||
// remove: grantRemove,
|
useEffect(() => {
|
||||||
// } = useFieldArray({
|
calculateHashedValue();
|
||||||
// control,
|
}, [namespace, calculateHashedValue]);
|
||||||
// name: "HookGrants", // unique name for your Field Array
|
|
||||||
// });
|
|
||||||
const [hashedNamespace, setHashedNamespace] = useState("");
|
|
||||||
const namespace = watch(
|
|
||||||
"HookNamespace",
|
|
||||||
snap.files?.[snap.active]?.name?.split(".")?.[0] || ""
|
|
||||||
);
|
|
||||||
const calculateHashedValue = useCallback(async () => {
|
|
||||||
const hashedVal = await sha256(namespace);
|
|
||||||
setHashedNamespace(hashedVal.toUpperCase());
|
|
||||||
}, [namespace]);
|
|
||||||
useEffect(() => {
|
|
||||||
calculateHashedValue();
|
|
||||||
}, [namespace, calculateHashedValue]);
|
|
||||||
|
|
||||||
if (!account) {
|
// Calcucate initial fee estimate when modal opens
|
||||||
return null;
|
useEffect(() => {
|
||||||
}
|
if (formInitialized && account) {
|
||||||
|
(async () => {
|
||||||
const onSubmit: SubmitHandler<SetHookData> = async (data) => {
|
const formValues = getValues();
|
||||||
const currAccount = state.accounts.find(
|
const tx = await prepareDeployHookTx(account, formValues);
|
||||||
(acc) => acc.address === account.address
|
if (!tx) {
|
||||||
);
|
return;
|
||||||
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
|
|
||||||
}
|
}
|
||||||
>
|
const res = await estimateFee(tx, account);
|
||||||
Set Hook
|
if (res && res.base_fee) {
|
||||||
</Button>
|
setValue("Fee", res.base_fee);
|
||||||
</DialogTrigger>
|
}
|
||||||
<DialogContent>
|
})();
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
}
|
||||||
<DialogTitle>Deploy configuration</DialogTitle>
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
<DialogDescription as="div">
|
}, [formInitialized]);
|
||||||
<Stack css={{ width: "100%", flex: 1 }}>
|
|
||||||
<Box css={{ width: "100%" }}>
|
if (!account) {
|
||||||
<Label>Invoke on transactions</Label>
|
return null;
|
||||||
<Controller
|
}
|
||||||
name="Invoke"
|
|
||||||
control={control}
|
const onSubmit: SubmitHandler<SetHookData> = async (data) => {
|
||||||
defaultValue={transactionOptions.filter(
|
const currAccount = state.accounts.find(
|
||||||
(to) => to.label === "ttPAYMENT"
|
(acc) => acc.address === account.address
|
||||||
)}
|
);
|
||||||
render={({ field }) => (
|
if (currAccount) currAccount.isLoading = true;
|
||||||
<Select
|
const res = await deployHook(account, data);
|
||||||
{...field}
|
if (currAccount) currAccount.isLoading = false;
|
||||||
closeMenuOnSelect={false}
|
|
||||||
isMulti
|
if (res && res.engine_result === "tesSUCCESS") {
|
||||||
menuPosition="fixed"
|
toast.success("Transaction succeeded!");
|
||||||
options={transactionOptions}
|
return setIsSetHookDialogOpen(false);
|
||||||
/>
|
}
|
||||||
)}
|
toast.error(`Transaction failed! (${res?.engine_result_message})`);
|
||||||
/>
|
};
|
||||||
</Box>
|
return (
|
||||||
<Box css={{ width: "100%" }}>
|
<Dialog open={isSetHookDialogOpen} onOpenChange={setIsSetHookDialogOpen}>
|
||||||
<Label>Hook Namespace Seed</Label>
|
<DialogTrigger asChild>
|
||||||
<Input
|
<Button
|
||||||
{...register("HookNamespace", { required: true })}
|
ghost
|
||||||
autoComplete={"off"}
|
size="xs"
|
||||||
defaultValue={
|
uppercase
|
||||||
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || ""
|
variant={"secondary"}
|
||||||
}
|
disabled={
|
||||||
/>
|
account.isLoading ||
|
||||||
{errors.HookNamespace?.type === "required" && (
|
!snap.files.filter((file) => file.compiledWatContent).length
|
||||||
<Box css={{ display: "inline", color: "$red11" }}>
|
}
|
||||||
Namespace is required
|
>
|
||||||
</Box>
|
Set Hook
|
||||||
)}
|
</Button>
|
||||||
<Box css={{ mt: "$3" }}>
|
</DialogTrigger>
|
||||||
<Label>Hook Namespace (sha256)</Label>
|
<DialogContent>
|
||||||
<Input readOnly value={hashedNamespace} />
|
<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>
|
||||||
</Box>
|
<Box css={{ width: "100%" }}>
|
||||||
<Box css={{ width: "100%" }}>
|
<Label>Hook Namespace Seed</Label>
|
||||||
<Label style={{ marginBottom: "10px", display: "block" }}>
|
<Input
|
||||||
Hook parameters
|
{...register("HookNamespace", { required: true })}
|
||||||
</Label>
|
autoComplete={"off"}
|
||||||
<Stack>
|
defaultValue={
|
||||||
{fields.map((field, index) => (
|
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || ""
|
||||||
<Stack key={field.id}>
|
|
||||||
<Input
|
|
||||||
// important to include key with field's id
|
|
||||||
placeholder="Parameter name"
|
|
||||||
{...register(
|
|
||||||
`HookParameters.${index}.HookParameter.HookParameterName`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
placeholder="Value (hex-quoted)"
|
|
||||||
{...register(
|
|
||||||
`HookParameters.${index}.HookParameter.HookParameterValue`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button onClick={() => remove(index)} variant="destroy">
|
|
||||||
<Trash weight="regular" size="16px" />
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
))}
|
|
||||||
<Button
|
|
||||||
outline
|
|
||||||
fullWidth
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
append({
|
|
||||||
HookParameter: {
|
|
||||||
HookParameterName: "",
|
|
||||||
HookParameterValue: "",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
<Plus size="16px" />
|
{errors.HookNamespace?.type === "required" && (
|
||||||
Add Hook Parameter
|
<Box css={{ display: "inline", color: "$red11" }}>
|
||||||
</Button>
|
Namespace is required
|
||||||
</Stack>
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
{/* <Box css={{ width: "100%" }}>
|
<Box css={{ mt: "$3" }}>
|
||||||
|
<Label>Hook Namespace (sha256)</Label>
|
||||||
|
<Input readOnly value={hashedNamespace} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box css={{ width: "100%" }}>
|
||||||
|
<Label style={{ marginBottom: "10px", display: "block" }}>
|
||||||
|
Hook parameters
|
||||||
|
</Label>
|
||||||
|
<Stack>
|
||||||
|
{fields.map((field, index) => (
|
||||||
|
<Stack key={field.id}>
|
||||||
|
<Input
|
||||||
|
// important to include key with field's id
|
||||||
|
placeholder="Parameter name"
|
||||||
|
{...register(
|
||||||
|
`HookParameters.${index}.HookParameter.HookParameterName`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="Value (hex-quoted)"
|
||||||
|
{...register(
|
||||||
|
`HookParameters.${index}.HookParameter.HookParameterValue`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button onClick={() => remove(index)} variant="destroy">
|
||||||
|
<Trash weight="regular" size="16px" />
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
outline
|
||||||
|
fullWidth
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
append({
|
||||||
|
HookParameter: {
|
||||||
|
HookParameterName: "",
|
||||||
|
HookParameterValue: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Plus size="16px" />
|
||||||
|
Add Hook Parameter
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
<Box css={{ width: "100%", position: "relative" }}>
|
||||||
|
<Label>Fee</Label>
|
||||||
|
<Box css={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
{...register("Fee", { required: true })}
|
||||||
|
autoComplete={"off"}
|
||||||
|
defaultValue={10000}
|
||||||
|
css={{
|
||||||
|
"-moz-appearance": "textfield",
|
||||||
|
"&::-webkit-outer-spin-button": {
|
||||||
|
"-webkit-appearance": "none",
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
"&::-webkit-inner-spin-button ": {
|
||||||
|
"-webkit-appearance": "none",
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="primary"
|
||||||
|
outline
|
||||||
|
isLoading={estimateLoading}
|
||||||
|
css={{
|
||||||
|
position: "absolute",
|
||||||
|
right: "$2",
|
||||||
|
fontSize: "$xs",
|
||||||
|
cursor: "pointer",
|
||||||
|
alignContent: "center",
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setEstimateLoading(true);
|
||||||
|
const formValues = getValues();
|
||||||
|
try {
|
||||||
|
const tx = await prepareDeployHookTx(
|
||||||
|
account,
|
||||||
|
formValues
|
||||||
|
);
|
||||||
|
if (tx) {
|
||||||
|
const res = await estimateFee(tx, account);
|
||||||
|
|
||||||
|
if (res && res.base_fee) {
|
||||||
|
setValue("Fee", res.base_fee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {}
|
||||||
|
|
||||||
|
setEstimateLoading(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Suggest
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
{errors.Fee?.type === "required" && (
|
||||||
|
<Box css={{ display: "inline", color: "$red11" }}>
|
||||||
|
Fee is required
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{/* <Box css={{ width: "100%" }}>
|
||||||
<label style={{ marginBottom: "10px", display: "block" }}>
|
<label style={{ marginBottom: "10px", display: "block" }}>
|
||||||
Hook Grants
|
Hook Grants
|
||||||
</label>
|
</label>
|
||||||
@@ -269,38 +360,41 @@ export const SetHookDialog: React.FC<{ account: IAccount }> = ({ account }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box> */}
|
</Box> */}
|
||||||
</Stack>
|
</Stack>
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
css={{
|
css={{
|
||||||
marginTop: 25,
|
marginTop: 25,
|
||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
gap: "$3",
|
gap: "$3",
|
||||||
}}
|
}}
|
||||||
>
|
|
||||||
<DialogClose asChild>
|
|
||||||
<Button outline>Cancel</Button>
|
|
||||||
</DialogClose>
|
|
||||||
{/* <DialogClose asChild> */}
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
type="submit"
|
|
||||||
isLoading={account.isLoading}
|
|
||||||
>
|
>
|
||||||
Set Hook
|
<DialogClose asChild>
|
||||||
</Button>
|
<Button outline>Cancel</Button>
|
||||||
{/* </DialogClose> */}
|
</DialogClose>
|
||||||
</Flex>
|
{/* <DialogClose asChild> */}
|
||||||
<DialogClose asChild>
|
<Button
|
||||||
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
|
variant="primary"
|
||||||
<X size="20px" />
|
type="submit"
|
||||||
</Box>
|
isLoading={account.isLoading}
|
||||||
</DialogClose>
|
>
|
||||||
</form>
|
Set Hook
|
||||||
</DialogContent>
|
</Button>
|
||||||
</Dialog>
|
{/* </DialogClose> */}
|
||||||
);
|
</Flex>
|
||||||
};
|
<DialogClose asChild>
|
||||||
|
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
|
||||||
|
<X size="20px" />
|
||||||
|
</Box>
|
||||||
|
</DialogClose>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
SetHookDialog.displayName = "SetHookDialog";
|
||||||
|
|
||||||
export default SetHookDialog;
|
export default SetHookDialog;
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import Button from "../Button";
|
|||||||
import Flex from "../Flex";
|
import Flex from "../Flex";
|
||||||
import { TxJson } from "./json";
|
import { TxJson } from "./json";
|
||||||
import { TxUI } from "./ui";
|
import { TxUI } from "./ui";
|
||||||
|
import { default as _estimateFee } from "../../utils/estimateFee";
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export interface TransactionProps {
|
export interface TransactionProps {
|
||||||
header: string;
|
header: string;
|
||||||
@@ -76,13 +78,19 @@ const Transaction: FC<TransactionProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
setState({ txIsDisabled: false });
|
setState({ txIsDisabled: false });
|
||||||
}
|
}
|
||||||
}, [selectedAccount?.value, selectedTransaction?.value, setState, txIsLoading]);
|
}, [
|
||||||
|
selectedAccount?.value,
|
||||||
|
selectedTransaction?.value,
|
||||||
|
setState,
|
||||||
|
txIsLoading,
|
||||||
|
]);
|
||||||
|
|
||||||
const submitTest = useCallback(async () => {
|
const submitTest = useCallback(async () => {
|
||||||
let st: TransactionState | undefined;
|
let st: TransactionState | undefined;
|
||||||
|
const tt = txState.selectedTransaction?.value;
|
||||||
if (viewType === "json") {
|
if (viewType === "json") {
|
||||||
// save the editor state first
|
// save the editor state first
|
||||||
const pst = prepareState(editorValue || '', txState);
|
const pst = prepareState(editorValue || "", tt);
|
||||||
if (!pst) return;
|
if (!pst) return;
|
||||||
|
|
||||||
st = setState(pst);
|
st = setState(pst);
|
||||||
@@ -102,7 +110,7 @@ const Transaction: FC<TransactionProps> = ({
|
|||||||
const options = prepareOptions(st);
|
const options = prepareOptions(st);
|
||||||
|
|
||||||
if (options.Destination === null) {
|
if (options.Destination === null) {
|
||||||
throw Error("Destination account cannot be null")
|
throw Error("Destination account cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendTransaction(account, options, { logPrefix });
|
await sendTransaction(account, options, { logPrefix });
|
||||||
@@ -116,7 +124,17 @@ const Transaction: FC<TransactionProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setState({ txIsLoading: false });
|
setState({ txIsLoading: false });
|
||||||
}, [viewType, accounts, txIsDisabled, setState, header, editorValue, txState, selectedAccount?.value, prepareOptions]);
|
}, [
|
||||||
|
viewType,
|
||||||
|
accounts,
|
||||||
|
txIsDisabled,
|
||||||
|
setState,
|
||||||
|
header,
|
||||||
|
editorValue,
|
||||||
|
txState,
|
||||||
|
selectedAccount?.value,
|
||||||
|
prepareOptions,
|
||||||
|
]);
|
||||||
|
|
||||||
const resetState = useCallback(() => {
|
const resetState = useCallback(() => {
|
||||||
modifyTransaction(header, { viewType }, { replaceState: true });
|
modifyTransaction(header, { viewType }, { replaceState: true });
|
||||||
@@ -129,6 +147,31 @@ const Transaction: FC<TransactionProps> = ({
|
|||||||
[editorSavedValue, editorSettings.tabSize, prepareOptions]
|
[editorSavedValue, editorSettings.tabSize, prepareOptions]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const estimateFee = useCallback(
|
||||||
|
async (st?: TransactionState, opts?: { silent?: boolean }) => {
|
||||||
|
const state = st || txState;
|
||||||
|
const ptx = prepareOptions(state);
|
||||||
|
const account = accounts.find(
|
||||||
|
acc => acc.address === state.selectedAccount?.value
|
||||||
|
);
|
||||||
|
if (!account) {
|
||||||
|
if (!opts?.silent) {
|
||||||
|
toast.error("Please select account from the list.")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
};
|
||||||
|
|
||||||
|
ptx.Account = account.address;
|
||||||
|
ptx.Sequence = account.sequence;
|
||||||
|
|
||||||
|
const res = await _estimateFee(ptx, account, opts);
|
||||||
|
const fee = res?.base_fee;
|
||||||
|
setState({ estimatedFee: fee });
|
||||||
|
return fee;
|
||||||
|
},
|
||||||
|
[accounts, prepareOptions, setState, txState]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
|
<Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
|
||||||
{viewType === "json" ? (
|
{viewType === "json" ? (
|
||||||
@@ -137,9 +180,10 @@ const Transaction: FC<TransactionProps> = ({
|
|||||||
header={header}
|
header={header}
|
||||||
state={txState}
|
state={txState}
|
||||||
setState={setState}
|
setState={setState}
|
||||||
|
estimateFee={estimateFee}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<TxUI state={txState} setState={setState} />
|
<TxUI state={txState} setState={setState} estimateFee={estimateFee} />
|
||||||
)}
|
)}
|
||||||
<Flex
|
<Flex
|
||||||
row
|
row
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ interface JsonProps {
|
|||||||
header?: string;
|
header?: string;
|
||||||
setState: (pTx?: Partial<TransactionState> | undefined) => void;
|
setState: (pTx?: Partial<TransactionState> | undefined) => void;
|
||||||
state: TransactionState;
|
state: TransactionState;
|
||||||
|
estimateFee?: () => Promise<string | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TxJson: FC<JsonProps> = ({
|
export const TxJson: FC<JsonProps> = ({
|
||||||
@@ -38,22 +39,37 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
setState,
|
setState,
|
||||||
}) => {
|
}) => {
|
||||||
const { editorSettings, accounts } = useSnapshot(state);
|
const { editorSettings, accounts } = useSnapshot(state);
|
||||||
const { editorValue = value, selectedTransaction } = txState;
|
const { editorValue = value, estimatedFee } = txState;
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const [hasUnsaved, setHasUnsaved] = useState(false);
|
const [hasUnsaved, setHasUnsaved] = useState(false);
|
||||||
|
const [currTxType, setCurrTxType] = useState<string | undefined>(
|
||||||
|
txState.selectedTransaction?.value
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setState({ editorValue: value });
|
setState({ editorValue: value });
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const parsed = parseJSON(editorValue);
|
||||||
|
if (!parsed) return;
|
||||||
|
|
||||||
|
const tt = parsed.TransactionType;
|
||||||
|
const tx = transactionsData.find(t => t.TransactionType === tt);
|
||||||
|
if (tx) setCurrTxType(tx.TransactionType);
|
||||||
|
else {
|
||||||
|
setCurrTxType(undefined);
|
||||||
|
}
|
||||||
|
}, [editorValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editorValue === value) setHasUnsaved(false);
|
if (editorValue === value) setHasUnsaved(false);
|
||||||
else setHasUnsaved(true);
|
else setHasUnsaved(true);
|
||||||
}, [editorValue, value]);
|
}, [editorValue, value]);
|
||||||
|
|
||||||
const saveState = (value: string, txState: TransactionState) => {
|
const saveState = (value: string, transactionType?: string) => {
|
||||||
const tx = prepareState(value, txState);
|
const tx = prepareState(value, transactionType);
|
||||||
if (tx) setState(tx);
|
if (tx) setState(tx);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -68,7 +84,7 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
const onExit = (value: string) => {
|
const onExit = (value: string) => {
|
||||||
const options = parseJSON(value);
|
const options = parseJSON(value);
|
||||||
if (options) {
|
if (options) {
|
||||||
saveState(value, txState);
|
saveState(value, currTxType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showAlert("Error!", {
|
showAlert("Error!", {
|
||||||
@@ -82,9 +98,10 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
const path = `file:///${header}`;
|
const path = `file:///${header}`;
|
||||||
const monaco = useMonaco();
|
const monaco = useMonaco();
|
||||||
|
|
||||||
const getSchemas = useCallback((): any[] => {
|
const getSchemas = useCallback(async (): Promise<any[]> => {
|
||||||
const tt = selectedTransaction?.value;
|
const txObj = transactionsData.find(
|
||||||
const txObj = transactionsData.find(td => td.TransactionType === tt);
|
td => td.TransactionType === currTxType
|
||||||
|
);
|
||||||
|
|
||||||
let genericSchemaProps: any;
|
let genericSchemaProps: any;
|
||||||
if (txObj) {
|
if (txObj) {
|
||||||
@@ -98,7 +115,6 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
uri: "file:///main-schema.json", // id of the first schema
|
uri: "file:///main-schema.json", // id of the first schema
|
||||||
@@ -130,6 +146,9 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
Amount: {
|
Amount: {
|
||||||
$ref: "file:///amount-schema.json",
|
$ref: "file:///amount-schema.json",
|
||||||
},
|
},
|
||||||
|
Fee: {
|
||||||
|
$ref: "file:///fee-schema.json",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -141,17 +160,30 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
enum: accounts.map(acc => acc.address),
|
enum: accounts.map(acc => acc.address),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
uri: "file:///fee-schema.json",
|
||||||
|
schema: {
|
||||||
|
type: "string",
|
||||||
|
title: "Fee type",
|
||||||
|
const: estimatedFee,
|
||||||
|
description: estimatedFee
|
||||||
|
? "Above mentioned value is recommended base fee"
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
...amountSchema,
|
...amountSchema,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [accounts, header, selectedTransaction?.value]);
|
}, [accounts, currTxType, estimatedFee, header]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!monaco) return;
|
if (!monaco) return;
|
||||||
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
getSchemas().then(schemas => {
|
||||||
validate: true,
|
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||||
schemas: getSchemas(),
|
validate: true,
|
||||||
|
schemas,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}, [getSchemas, monaco]);
|
}, [getSchemas, monaco]);
|
||||||
|
|
||||||
@@ -184,19 +216,13 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
// register onExit cb
|
// register onExit cb
|
||||||
const model = editor.getModel();
|
const model = editor.getModel();
|
||||||
model?.onWillDispose(() => onExit(model.getValue()));
|
model?.onWillDispose(() => onExit(model.getValue()));
|
||||||
|
|
||||||
// set json defaults
|
|
||||||
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
|
||||||
validate: true,
|
|
||||||
schemas: getSchemas(),
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
theme={theme === "dark" ? "dark" : "light"}
|
theme={theme === "dark" ? "dark" : "light"}
|
||||||
/>
|
/>
|
||||||
{hasUnsaved && (
|
{hasUnsaved && (
|
||||||
<Text muted small css={{ position: "absolute", bottom: 0, right: 0 }}>
|
<Text muted small css={{ position: "absolute", bottom: 0, right: 0 }}>
|
||||||
This file has unsaved changes.{" "}
|
This file has unsaved changes.{" "}
|
||||||
<Link onClick={() => saveState(editorValue, txState)}>save</Link>{" "}
|
<Link onClick={() => saveState(editorValue, currTxType)}>save</Link>{" "}
|
||||||
<Link onClick={discardChanges}>discard</Link>
|
<Link onClick={discardChanges}>discard</Link>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FC } from "react";
|
import { FC, useCallback, useState } from "react";
|
||||||
import Container from "../Container";
|
import Container from "../Container";
|
||||||
import Flex from "../Flex";
|
import Flex from "../Flex";
|
||||||
import Input from "../Input";
|
import Input from "../Input";
|
||||||
@@ -9,17 +9,26 @@ import {
|
|||||||
TransactionState,
|
TransactionState,
|
||||||
transactionsData,
|
transactionsData,
|
||||||
TxFields,
|
TxFields,
|
||||||
|
getTxFields,
|
||||||
} from "../../state/transactions";
|
} from "../../state/transactions";
|
||||||
import { useSnapshot } from "valtio";
|
import { useSnapshot } from "valtio";
|
||||||
import state from "../../state";
|
import state from "../../state";
|
||||||
import { streamState } from "../DebugStream";
|
import { streamState } from "../DebugStream";
|
||||||
|
import { Button } from "..";
|
||||||
|
|
||||||
interface UIProps {
|
interface UIProps {
|
||||||
setState: (pTx?: Partial<TransactionState> | undefined) => void;
|
setState: (
|
||||||
|
pTx?: Partial<TransactionState> | undefined
|
||||||
|
) => TransactionState | undefined;
|
||||||
state: TransactionState;
|
state: TransactionState;
|
||||||
|
estimateFee?: (...arg: any) => Promise<string | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TxUI: FC<UIProps> = ({ state: txState, setState }) => {
|
export const TxUI: FC<UIProps> = ({
|
||||||
|
state: txState,
|
||||||
|
setState,
|
||||||
|
estimateFee,
|
||||||
|
}) => {
|
||||||
const { accounts } = useSnapshot(state);
|
const { accounts } = useSnapshot(state);
|
||||||
const {
|
const {
|
||||||
selectedAccount,
|
selectedAccount,
|
||||||
@@ -45,32 +54,54 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState }) => {
|
|||||||
}))
|
}))
|
||||||
.filter(acc => acc.value !== selectedAccount?.value);
|
.filter(acc => acc.value !== selectedAccount?.value);
|
||||||
|
|
||||||
const resetOptions = (tt: string) => {
|
const [feeLoading, setFeeLoading] = useState(false);
|
||||||
const txFields: TxFields | undefined = transactionsData.find(
|
|
||||||
tx => tx.TransactionType === tt
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!txFields) return setState({ txFields: {} });
|
const resetOptions = useCallback(
|
||||||
|
(tt: string) => {
|
||||||
const _txFields = Object.keys(txFields)
|
const fields = getTxFields(tt);
|
||||||
.filter(key => !["TransactionType", "Account", "Sequence"].includes(key))
|
if (!fields.Destination) setState({ selectedDestAccount: null });
|
||||||
.reduce<TxFields>(
|
return setState({ txFields: fields });
|
||||||
(tf, key) => ((tf[key as keyof TxFields] = (txFields as any)[key]), tf),
|
},
|
||||||
{}
|
[setState]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!_txFields.Destination) setState({ selectedDestAccount: null });
|
|
||||||
setState({ txFields: _txFields });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSetAccount = (acc: SelectOption) => {
|
const handleSetAccount = (acc: SelectOption) => {
|
||||||
setState({ selectedAccount: acc });
|
setState({ selectedAccount: acc });
|
||||||
streamState.selectedAccount = acc;
|
streamState.selectedAccount = acc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSetField = useCallback(
|
||||||
|
(field: keyof TxFields, value: string, opFields?: TxFields) => {
|
||||||
|
const fields = opFields || txFields;
|
||||||
|
const obj = fields[field];
|
||||||
|
setState({
|
||||||
|
txFields: {
|
||||||
|
...fields,
|
||||||
|
[field]: typeof obj === "object" ? { ...obj, $value: value } : value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setState, txFields]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEstimateFee = useCallback(
|
||||||
|
async (state?: TransactionState, silent?: boolean) => {
|
||||||
|
setFeeLoading(true);
|
||||||
|
|
||||||
|
const fee = await estimateFee?.(state, { silent });
|
||||||
|
if (fee) handleSetField("Fee", fee, state?.txFields);
|
||||||
|
|
||||||
|
setFeeLoading(false);
|
||||||
|
},
|
||||||
|
[estimateFee, handleSetField]
|
||||||
|
);
|
||||||
|
|
||||||
const handleChangeTxType = (tt: SelectOption) => {
|
const handleChangeTxType = (tt: SelectOption) => {
|
||||||
setState({ selectedTransaction: tt });
|
setState({ selectedTransaction: tt });
|
||||||
resetOptions(tt.value);
|
|
||||||
|
const newState = resetOptions(tt.value);
|
||||||
|
|
||||||
|
handleEstimateFee(newState, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const specialFields = ["TransactionType", "Account", "Destination"];
|
const specialFields = ["TransactionType", "Account", "Destination"];
|
||||||
@@ -87,7 +118,7 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState }) => {
|
|||||||
height: "calc(100% - 45px)",
|
height: "calc(100% - 45px)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex column fluid css={{ height: "100%", overflowY: "auto" }}>
|
<Flex column fluid css={{ height: "100%", overflowY: "auto", pr: "$1" }}>
|
||||||
<Flex
|
<Flex
|
||||||
row
|
row
|
||||||
fluid
|
fluid
|
||||||
@@ -174,36 +205,49 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let isXrp = typeof _value === "object" && _value.$type === "xrp";
|
let isXrp = typeof _value === "object" && _value.$type === "xrp";
|
||||||
|
|
||||||
|
const isFee = field === "Fee";
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex column key={field} css={{ mb: "$2", pr: "1px" }}>
|
||||||
key={field}
|
<Flex
|
||||||
row
|
row
|
||||||
fluid
|
fluid
|
||||||
css={{
|
css={{
|
||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
mb: "$3",
|
position: "relative",
|
||||||
pr: "1px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text muted css={{ mr: "$3" }}>
|
|
||||||
{field + (isXrp ? " (XRP)" : "")}:{" "}
|
|
||||||
</Text>
|
|
||||||
<Input
|
|
||||||
value={value}
|
|
||||||
onChange={e => {
|
|
||||||
setState({
|
|
||||||
txFields: {
|
|
||||||
...txFields,
|
|
||||||
[field]:
|
|
||||||
typeof _value === "object"
|
|
||||||
? { ..._value, $value: e.target.value }
|
|
||||||
: e.target.value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
css={{ width: "70%", flex: "inherit" }}
|
>
|
||||||
/>
|
<Text muted css={{ mr: "$3" }}>
|
||||||
|
{field + (isXrp ? " (XRP)" : "")}:{" "}
|
||||||
|
</Text>
|
||||||
|
<Input
|
||||||
|
value={value}
|
||||||
|
onChange={e => {
|
||||||
|
handleSetField(field, e.target.value);
|
||||||
|
}}
|
||||||
|
css={{ width: "70%", flex: "inherit" }}
|
||||||
|
/>
|
||||||
|
{isFee && (
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="primary"
|
||||||
|
outline
|
||||||
|
isLoading={feeLoading}
|
||||||
|
css={{
|
||||||
|
position: "absolute",
|
||||||
|
right: "$2",
|
||||||
|
fontSize: "$xs",
|
||||||
|
cursor: "pointer",
|
||||||
|
alignContent: "center",
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
onClick={() => handleEstimateFee()}
|
||||||
|
>
|
||||||
|
Suggest
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
12057
package-lock.json
generated
12057
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -50,11 +50,7 @@ function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* deployHook function turns the wasm binary into
|
export const prepareDeployHookTx = async (
|
||||||
* hex string, signs the transaction and deploys it to
|
|
||||||
* Hooks testnet.
|
|
||||||
*/
|
|
||||||
export const deployHook = async (
|
|
||||||
account: IAccount & { name?: string },
|
account: IAccount & { name?: string },
|
||||||
data: SetHookData
|
data: SetHookData
|
||||||
) => {
|
) => {
|
||||||
@@ -93,13 +89,12 @@ export const deployHook = async (
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
const tx = {
|
const tx = {
|
||||||
Account: account.address,
|
Account: account.address,
|
||||||
TransactionType: "SetHook",
|
TransactionType: "SetHook",
|
||||||
Sequence: account.sequence,
|
Sequence: account.sequence,
|
||||||
Fee: "100000",
|
Fee: data.Fee,
|
||||||
Hooks: [
|
Hooks: [
|
||||||
{
|
{
|
||||||
Hook: {
|
Hook: {
|
||||||
@@ -118,15 +113,28 @@ export const deployHook = async (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const keypair = derive.familySeed(account.secret);
|
/* deployHook function turns the wasm binary into
|
||||||
try {
|
* hex string, signs the transaction and deploys it to
|
||||||
// Update tx Fee value with network estimation
|
* Hooks testnet.
|
||||||
await estimateFee(tx, keypair);
|
*/
|
||||||
} catch (err) {
|
export const deployHook = async (
|
||||||
// use default value what you defined earlier
|
account: IAccount & { name?: string },
|
||||||
console.log(err);
|
data: SetHookData
|
||||||
|
) => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
const tx = await prepareDeployHookTx(account, data);
|
||||||
|
if (!tx) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (!state.client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const keypair = derive.familySeed(account.secret);
|
||||||
|
|
||||||
const { signedTransaction } = sign(tx, keypair);
|
const { signedTransaction } = sign(tx, keypair);
|
||||||
const currentAccount = state.accounts.find(
|
const currentAccount = state.accounts.find(
|
||||||
(acc) => acc.address === account.address
|
(acc) => acc.address === account.address
|
||||||
@@ -137,7 +145,7 @@ export const deployHook = async (
|
|||||||
let submitRes;
|
let submitRes;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
submitRes = await state.client.send({
|
submitRes = await state.client?.send({
|
||||||
command: "submit",
|
command: "submit",
|
||||||
tx_blob: signedTransaction,
|
tx_blob: signedTransaction,
|
||||||
});
|
});
|
||||||
@@ -216,7 +224,8 @@ export const deleteHook = async (account: IAccount & { name?: string }) => {
|
|||||||
const keypair = derive.familySeed(account.secret);
|
const keypair = derive.familySeed(account.secret);
|
||||||
try {
|
try {
|
||||||
// Update tx Fee value with network estimation
|
// Update tx Fee value with network estimation
|
||||||
await estimateFee(tx, keypair);
|
const res = await estimateFee(tx, account);
|
||||||
|
tx["Fee"] = res?.base_fee ? res?.base_fee : "1000";
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// use default value what you defined earlier
|
// use default value what you defined earlier
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ export const sendTransaction = async (account: IAccount, txOptions: TransactionO
|
|||||||
const { Fee = "1000", ...opts } = txOptions
|
const { Fee = "1000", ...opts } = txOptions
|
||||||
const tx: TransactionOptions = {
|
const tx: TransactionOptions = {
|
||||||
Account: account.address,
|
Account: account.address,
|
||||||
Sequence: account.sequence, // TODO auto-fillable
|
Sequence: account.sequence,
|
||||||
Fee, // TODO auto-fillable
|
Fee, // TODO auto-fillable default
|
||||||
...opts
|
...opts
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ export interface TransactionState {
|
|||||||
txFields: TxFields;
|
txFields: TxFields;
|
||||||
viewType: 'json' | 'ui',
|
viewType: 'json' | 'ui',
|
||||||
editorSavedValue: null | string,
|
editorSavedValue: null | string,
|
||||||
editorValue?: string
|
editorValue?: string,
|
||||||
|
estimatedFee?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ export const modifyTransaction = (
|
|||||||
Object.keys(partialTx).forEach(k => {
|
Object.keys(partialTx).forEach(k => {
|
||||||
// Typescript mess here, but is definetly safe!
|
// Typescript mess here, but is definetly safe!
|
||||||
const s = tx.state as any;
|
const s = tx.state as any;
|
||||||
const p = partialTx as any;
|
const p = partialTx as any; // ? Make copy
|
||||||
if (!deepEqual(s[k], p[k])) s[k] = p[k];
|
if (!deepEqual(s[k], p[k])) s[k] = p[k];
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -140,7 +141,7 @@ export const prepareTransaction = (data: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// editor value to state
|
// editor value to state
|
||||||
export const prepareState = (value: string, txState: TransactionState) => {
|
export const prepareState = (value: string, transactionType?: string) => {
|
||||||
const options = parseJSON(value);
|
const options = parseJSON(value);
|
||||||
if (!options) {
|
if (!options) {
|
||||||
showAlert("Error!", {
|
showAlert("Error!", {
|
||||||
@@ -151,7 +152,7 @@ export const prepareState = (value: string, txState: TransactionState) => {
|
|||||||
|
|
||||||
const { Account, TransactionType, Destination, ...rest } = options;
|
const { Account, TransactionType, Destination, ...rest } = options;
|
||||||
let tx: Partial<TransactionState> = {};
|
let tx: Partial<TransactionState> = {};
|
||||||
const { txFields } = txState
|
const txFields = getTxFields(transactionType)
|
||||||
|
|
||||||
if (Account) {
|
if (Account) {
|
||||||
const acc = state.accounts.find(acc => acc.address === Account);
|
const acc = state.accounts.find(acc => acc.address === Account);
|
||||||
@@ -206,7 +207,7 @@ export const prepareState = (value: string, txState: TransactionState) => {
|
|||||||
if (isXrp) {
|
if (isXrp) {
|
||||||
rest[field] = {
|
rest[field] = {
|
||||||
$type: "xrp",
|
$type: "xrp",
|
||||||
$value: +value / 1000000, // TODO maybe use bigint?
|
$value: +value / 1000000, // ! maybe use bigint?
|
||||||
};
|
};
|
||||||
} else if (typeof value === "object") {
|
} else if (typeof value === "object") {
|
||||||
rest[field] = {
|
rest[field] = {
|
||||||
@@ -222,4 +223,24 @@ export const prepareState = (value: string, txState: TransactionState) => {
|
|||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getTxFields = (tt?: string) => {
|
||||||
|
const txFields: TxFields | undefined = transactionsData.find(
|
||||||
|
tx => tx.TransactionType === tt
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!txFields) return {}
|
||||||
|
|
||||||
|
let _txFields = Object.keys(txFields)
|
||||||
|
.filter(
|
||||||
|
key => !["TransactionType", "Account", "Sequence"].includes(key)
|
||||||
|
)
|
||||||
|
.reduce<TxFields>(
|
||||||
|
(tf, key) => (
|
||||||
|
(tf[key as keyof TxFields] = (txFields as any)[key]), tf
|
||||||
|
),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
return _txFields
|
||||||
|
}
|
||||||
|
|
||||||
export { transactionsData }
|
export { transactionsData }
|
||||||
|
|||||||
@@ -1,19 +1,29 @@
|
|||||||
import { sign, XRPL_Account } from "xrpl-accountlib"
|
import toast from 'react-hot-toast';
|
||||||
import state from "../state"
|
import { derive, sign } from "xrpl-accountlib"
|
||||||
|
import state, { IAccount } from "../state"
|
||||||
|
|
||||||
// Mutate tx object with network estimated fee value
|
const estimateFee = async (tx: Record<string, unknown>, account: IAccount, opts: { silent?: boolean } = {}): Promise<null | { base_fee: string, median_fee: string; minimum_fee: string; open_ledger_fee: string; }> => {
|
||||||
const estimateFee = async (tx: Record<string, unknown>, keypair: XRPL_Account): Promise<null | { base_fee: string, median_fee: string; minimum_fee: string; open_ledger_fee: string; }> => {
|
|
||||||
const copyTx = JSON.parse(JSON.stringify(tx))
|
|
||||||
delete copyTx['SigningPubKey']
|
|
||||||
const { signedTransaction } = sign(copyTx, keypair);
|
|
||||||
try {
|
try {
|
||||||
|
const copyTx = JSON.parse(JSON.stringify(tx))
|
||||||
|
delete copyTx['SigningPubKey']
|
||||||
|
if (!copyTx.Fee) {
|
||||||
|
copyTx.Fee = '1000'
|
||||||
|
}
|
||||||
|
|
||||||
|
const keypair = derive.familySeed(account.secret)
|
||||||
|
const { signedTransaction } = sign(copyTx, keypair);
|
||||||
|
|
||||||
const res = await state.client?.send({ command: 'fee', tx_blob: signedTransaction })
|
const res = await state.client?.send({ command: 'fee', tx_blob: signedTransaction })
|
||||||
if (res && res.drops) {
|
if (res && res.drops) {
|
||||||
return tx['Fee'] = res.drops.base_fee;
|
return res.drops;
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw Error('Cannot estimate fee')
|
if (!opts.silent) {
|
||||||
|
console.error(err)
|
||||||
|
toast.error("Cannot estimate fee.") // ? Some better msg
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user