From 9a90e4459165484dfde0a4b2b6e18bc38c72b8e4 Mon Sep 17 00:00:00 2001 From: "ildar.timerbaev" Date: Sat, 9 Nov 2024 14:56:59 +0300 Subject: [PATCH] Improved the hive engine transfer dialog --- src/api/operations.ts | 223 ------------------ .../engine-transfer-confirmation.tsx | 72 ++++++ .../engine-transfer-form-header.tsx | 5 +- .../engine-transfer-power-down.tsx | 9 +- .../engine-transfer/engine-transfer-sign.tsx | 73 ++++++ .../engine-transfer-step-1.tsx | 82 +++---- .../engine-transfer-success.tsx | 46 ++++ .../_components/engine-transfer/index.tsx | 58 ++++- .../[username]/engine/_mutations/index.ts | 3 + .../transfer-engine-by-hivesigner.ts | 31 +++ .../_mutations/transfer-engine-by-key.ts | 49 ++++ .../_mutations/transfer-engine-by-keychain.ts | 50 ++++ .../_mutations/transfer-get-operation.ts | 114 +++++++++ src/features/i18n/locales/en-US.json | 4 +- 14 files changed, 536 insertions(+), 283 deletions(-) create mode 100644 src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-confirmation.tsx create mode 100644 src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-sign.tsx create mode 100644 src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-success.tsx create mode 100644 src/app/(dynamicPages)/profile/[username]/engine/_mutations/index.ts create mode 100644 src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-engine-by-hivesigner.ts create mode 100644 src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-engine-by-key.ts create mode 100644 src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-engine-by-keychain.ts create mode 100644 src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-get-operation.ts diff --git a/src/api/operations.ts b/src/api/operations.ts index 30a2d4bfa..e7f8c50db 100644 --- a/src/api/operations.ts +++ b/src/api/operations.ts @@ -1422,85 +1422,6 @@ export const updatePassword = ( ownerKey: PrivateKey ): Promise => hiveClient.broadcast.updateAccount(update, ownerKey); -// HE Operations -export const transferHiveEngineKc = ( - from: string, - to: string, - symbol: string, - amount: string, - memo: string -) => { - const json = JSON.stringify({ - contractName: "tokens", - contractAction: "transfer", - contractPayload: { - symbol, - to, - quantity: amount.toString(), - memo - } - }); - - return keychain.customJson(from, "ssc-mainnet-hive", "Active", json, "Transfer"); -}; -export const delegateHiveEngineKc = (from: string, to: string, symbol: string, amount: string) => { - const json = JSON.stringify({ - contractName: "tokens", - contractAction: "delegate", - contractPayload: { - symbol, - to, - quantity: amount.toString() - } - }); - - return keychain.customJson(from, "ssc-mainnet-hive", "Active", json, "Transfer"); -}; -export const undelegateHiveEngineKc = ( - from: string, - to: string, - symbol: string, - amount: string -) => { - const json = JSON.stringify({ - contractName: "tokens", - contractAction: "undelegate", - contractPayload: { - symbol, - from: to, - quantity: amount.toString() - } - }); - - return keychain.customJson(from, "ssc-mainnet-hive", "Active", json, "Transfer"); -}; -export const stakeHiveEngineKc = (from: string, to: string, symbol: string, amount: string) => { - const json = JSON.stringify({ - contractName: "tokens", - contractAction: "stake", - contractPayload: { - symbol, - to, - quantity: amount.toString() - } - }); - - return keychain.customJson(from, "ssc-mainnet-hive", "Active", json, "Transfer"); -}; -export const unstakeHiveEngineKc = (from: string, to: string, symbol: string, amount: string) => { - const json = JSON.stringify({ - contractName: "tokens", - contractAction: "unstake", - contractPayload: { - symbol, - to, - quantity: amount.toString() - } - }); - - return keychain.customJson(from, "ssc-mainnet-hive", "Active", json, "Transfer"); -}; - // HE Hive Signer Operations export const transferHiveEngineHs = ( from: string, @@ -1629,150 +1550,6 @@ export const unstakeHiveEngineHs = ( return hotSign("custom-json", params, `@${from}/engine`); }; -//HE Key Operations -export const transferHiveEngineKey = async ( - from: string, - key: PrivateKey, - symbol: string, - to: string, - amount: string, - memo: string -): Promise => { - const json = JSON.stringify({ - contractName: "tokens", - contractAction: "transfer", - contractPayload: { - symbol, - to, - quantity: amount.toString(), - memo - } - }); - - const op = { - id: "ssc-mainnet-hive", - json, - required_auths: [from], - required_posting_auths: [] - }; - - const result = await hiveClient.broadcast.json(op, key); - - return result; -}; - -export const delegateHiveEngineKey = async ( - from: string, - key: PrivateKey, - symbol: string, - to: string, - amount: string -): Promise => { - const json = JSON.stringify({ - contractName: "tokens", - contractAction: "delegate", - contractPayload: { - symbol, - to, - quantity: amount.toString() - } - }); - - const op = { - id: "ssc-mainnet-hive", - json, - required_auths: [from], - required_posting_auths: [] - }; - - const result = await hiveClient.broadcast.json(op, key); - return result; -}; - -export const undelegateHiveEngineKey = async ( - from: string, - key: PrivateKey, - symbol: string, - to: string, - amount: string -): Promise => { - const json = JSON.stringify({ - contractName: "tokens", - contractAction: "undelegate", - contractPayload: { - symbol, - from: to, - quantity: amount.toString() - } - }); - - const op = { - id: "ssc-mainnet-hive", - json, - required_auths: [from], - required_posting_auths: [] - }; - - const result = await hiveClient.broadcast.json(op, key); - return result; -}; - -export const stakeHiveEngineKey = async ( - from: string, - key: PrivateKey, - symbol: string, - to: string, - amount: string -): Promise => { - const json = JSON.stringify({ - contractName: "tokens", - contractAction: "stake", - contractPayload: { - symbol, - to, - quantity: amount.toString() - } - }); - - const op = { - id: "ssc-mainnet-hive", - json, - required_auths: [from], - required_posting_auths: [] - }; - - const result = await hiveClient.broadcast.json(op, key); - return result; -}; - -export const unstakeHiveEngineKey = async ( - from: string, - key: PrivateKey, - symbol: string, - to: string, - amount: string -): Promise => { - const json = JSON.stringify({ - contractName: "tokens", - contractAction: "stake", - contractPayload: { - symbol, - to, - quantity: amount.toString() - } - }); - - const op = { - id: "ssc-mainnet-hive", - json, - required_auths: [from], - required_posting_auths: [] - }; - - const result = await hiveClient.broadcast.json(op, key); - return result; -}; - export const Revoke = ( account: string, weight_threshold: number, diff --git a/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-confirmation.tsx b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-confirmation.tsx new file mode 100644 index 000000000..25e9e094c --- /dev/null +++ b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-confirmation.tsx @@ -0,0 +1,72 @@ +import { EngineTransferFormHeader } from "@/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-form-header"; +import { UserAvatar } from "@/features/shared"; +import i18next from "i18next"; +import { useGlobalStore } from "@/core/global-store"; +import { Button } from "@/features/ui"; +import { useMemo } from "react"; +import { UilArrowRight } from "@tooni/iconscout-unicons-react"; + +interface Props { + titleLngKey: string; + onBack: () => void; + onConfirm: () => void; + to: string; + amount: string; + asset: string; + memo: string; + mode: string; +} + +export function EngineTransferConfirmation({ + titleLngKey, + onBack, + to, + amount, + asset, + memo, + onConfirm, + mode +}: Props) { + const activeUser = useGlobalStore((s) => s.activeUser); + + const showTo = useMemo( + () => ["transfer", "delegate", "undelegate", "stake"].includes(mode), + [mode] + ); + + return ( +
+ +
+
+
+ + {showTo && ( + <> + + + + )} +
+
+ {amount} + {asset} +
+ {memo &&
{memo}
} +
+
+ + +
+
+
+ ); +} diff --git a/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-form-header.tsx b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-form-header.tsx index 4dddf9772..26d9f87b0 100644 --- a/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-form-header.tsx +++ b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-form-header.tsx @@ -3,12 +3,13 @@ import i18next from "i18next"; interface Props { titleLngKey: string; subTitleLngKey: string; + step: number; } -export function EngineTransferFormHeader({ titleLngKey, subTitleLngKey }: Props) { +export function EngineTransferFormHeader({ titleLngKey, subTitleLngKey, step }: Props) { return (
-
1
+
{step}
{i18next.t(`transfer.${titleLngKey}`)}
{i18next.t(`transfer.${subTitleLngKey}`)}
diff --git a/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-power-down.tsx b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-power-down.tsx index a391f45ea..2f9c5beeb 100644 --- a/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-power-down.tsx +++ b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-power-down.tsx @@ -8,9 +8,16 @@ interface Props { subTitleLngKey: string; onNext: () => void; asset: string; + precision: number; } -export function EngineTransferPowerDown({ titleLngKey, subTitleLngKey, onNext, asset }: Props) { +export function EngineTransferPowerDown({ + titleLngKey, + subTitleLngKey, + onNext, + asset, + precision +}: Props) { const activeUserWallet = useActiveUserWallet(); return ( diff --git a/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-sign.tsx b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-sign.tsx new file mode 100644 index 000000000..6f71f0063 --- /dev/null +++ b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-sign.tsx @@ -0,0 +1,73 @@ +import { EngineTransferFormHeader } from "@/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-form-header"; +import i18next from "i18next"; +import { KeyOrHot } from "@/features/shared"; +import { + useTransferEngineByHivesigner, + useTransferEngineByKey, + useTransferEngineByKeychain +} from "@/app/(dynamicPages)/profile/[username]/engine/_mutations"; + +interface Props { + mode: string; + to: string; + amount: string; + asset: string; + memo: string; + onBack: () => void; + onNext: () => void; +} + +export function EngineTransferSign({ onBack, mode, asset, to, amount, memo, onNext }: Props) { + const { mutateAsync: signKey, isPending: isSigningByKey } = useTransferEngineByKey(onNext); + const { mutateAsync: signKeychain, isPending: isSigningByKeychain } = + useTransferEngineByKeychain(onNext); + const signHivesigner = useTransferEngineByHivesigner(onNext); + + return ( +
+ +
+ + signKey({ + key, + asset, + amount, + memo, + mode, + to + }) + } + onHot={() => + signHivesigner({ + asset, + amount, + memo, + mode, + to + }) + } + onKc={() => + signKeychain({ + asset, + amount, + memo, + mode, + to + }) + } + /> +

+ + {i18next.t("g.back")} + +

+
+
+ ); +} diff --git a/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-step-1.tsx b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-step-1.tsx index 59861a6a4..d337bbcac 100644 --- a/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-step-1.tsx +++ b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-step-1.tsx @@ -8,10 +8,9 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import numeral from "numeral"; import { cryptoUtils } from "@hiveio/dhive"; import { useDebounce } from "react-use"; -import { getAccountFullQuery, getDynamicPropsQuery, useHiveEngineAssetWallet } from "@/api/queries"; +import { getAccountFullQuery, useHiveEngineAssetWallet } from "@/api/queries"; import badActors from "@hiveio/hivescript/bad-actors.json"; import { amountFormatCheck } from "@/utils/amount-format-check"; -import { formattedNumber, parseAsset, vestsToHp } from "@/utils"; import { EngineTransferFormHeader } from "@/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-form-header"; interface Props { @@ -26,6 +25,7 @@ interface Props { setMemo: (memo: string) => void; asset: string; onNext: () => void; + precision: number; } export function EngineTransferStep1({ @@ -39,9 +39,11 @@ export function EngineTransferStep1({ setAmount, memo, setMemo, - onNext + onNext, + precision }: Props) { const activeUser = useGlobalStore((s) => s.activeUser); + const assetWallet = useHiveEngineAssetWallet(asset); const [toInput, setToInput] = useState(""); const [toDebouncedInput, setToDebouncedInput] = useState(""); @@ -49,10 +51,9 @@ export function EngineTransferStep1({ const [toWarning, setToWarning] = useState(); const [toError, setToError] = useState(); - const { data: dynamicProps } = getDynamicPropsQuery().useClientQuery(); - const { data: toData, isPending } = getAccountFullQuery(toDebouncedInput).useClientQuery(); + const { data: toData, isFetching } = getAccountFullQuery(toDebouncedInput).useClientQuery(); - const assetWallet = useHiveEngineAssetWallet(asset); + const assetBalance = useMemo(() => assetWallet?.balance ?? 0, [assetWallet]); const hive = useMemo(() => Math.round((Number(amount) / 13) * 1000) / 1000, [amount]); const showTo = useMemo( () => ["transfer", "delegate", "undelegate", "stake"].includes(mode), @@ -81,30 +82,13 @@ export function EngineTransferStep1({ } return ""; - }, [amount, assetWallet?.balance]); + }, [amount, assetWallet?.balance, precision]); const canSubmit = useMemo(() => { if (mode === "unstake") return parseFloat(amount) > 0; - return toData && !toError && !amountError && !memoError && !isPending && parseFloat(amount) > 0; - }, [amount, amountError, isPending, memoError, mode, toData, toError]); - - const delegateAccount = - delegationList && - delegationList.length > 0 && - delegationList!.find( - (item) => - (item as DelegateVestingShares).delegatee === to && - (item as DelegateVestingShares).delegator === activeUser?.username + return ( + toData && !toError && !amountError && !memoError && !isFetching && parseFloat(amount) > 0 ); - const previousAmount = delegateAccount - ? Number( - formattedNumber( - vestsToHp( - Number(parseAsset(delegateAccount!.vesting_shares).amount), - dynamicProps?.hivePerMVests ?? 0 - ) - ) - ) - : ""; + }, [amount, amountError, isFetching, memoError, mode, toData, toError]); useDebounce( () => { @@ -146,18 +130,14 @@ export function EngineTransferStep1({ [setMemo] ); - const handleTo = useCallback( - (e: React.ChangeEvent) => { - if (to === "") { - setToWarning(undefined); - setToError(undefined); - return; - } + const handleTo = useCallback((e: React.ChangeEvent) => { + if (e.target.value === "") { + setToWarning(undefined); + setToError(undefined); + } - setToInput(e.target.value); - }, - [to] - ); + setToInput(e.target.value); + }, []); const copyBalance = useCallback(() => { const amount = formatNumber(assetWallet?.balance ?? 0, precision); @@ -165,9 +145,13 @@ export function EngineTransferStep1({ }, [assetWallet?.balance, formatNumber, setAmount]); return ( -
- - {isPending && } +
+ + {isFetching && }
{mode !== "undelegate" && (
@@ -221,14 +205,14 @@ export function EngineTransferStep1({ placeholder={i18next.t("transfer.amount-placeholder")} value={amount} onChange={(e) => setAmount(e.target.value)} - className={amount > balance && amountError ? "is-invalid" : ""} + className={+amount > assetBalance && amountError ? "is-invalid" : ""} autoFocus={mode !== "transfer"} />
- {amountError && amount > balance && ( + {amountError && +amount > assetBalance && (
{amountError}
)} @@ -249,15 +233,6 @@ export function EngineTransferStep1({ {to.length > 0 && Number(amount) > 0 && toData?.__loaded && mode === "delegate" && (
{i18next.t("transfer.override-warning-1")} - {delegateAccount && ( - <> -
- {i18next.t("transfer.override-warning-2", { - account: to, - previousAmount: previousAmount - })} - - )}
)} {mode === "unstake" && !isNaN(hive) && hive > 0 && ( @@ -292,8 +267,7 @@ export function EngineTransferStep1({
- {/* Changed && to || since it just allows the form to submit anyway initially */} -
diff --git a/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-success.tsx b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-success.tsx new file mode 100644 index 000000000..00daf0585 --- /dev/null +++ b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-success.tsx @@ -0,0 +1,46 @@ +import { EngineTransferFormHeader } from "@/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-form-header"; +import i18next from "i18next"; +import { Button } from "@ui/button"; +import { useGlobalStore } from "@/core/global-store"; +import { UilCheckCircle } from "@tooni/iconscout-unicons-react"; + +interface Props { + mode: string; + amount: string; + asset: string; + onFinish: () => void; + onReset: () => void; + to: string; +} + +export function EngineTransferSuccess({ mode, onFinish, amount, asset, to, onReset }: Props) { + const activeUser = useGlobalStore((s) => s.activeUser); + + return ( +
+ +
+ +
+
+ + +
+
+
+ ); +} diff --git a/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/index.tsx b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/index.tsx index ac06740cc..08c531793 100644 --- a/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/index.tsx +++ b/src/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/index.tsx @@ -1,7 +1,11 @@ import { Modal, ModalBody, ModalHeader } from "@ui/modal"; -import { useMemo, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { EngineTransferStep1 } from "@/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-step-1"; import { useHiveEngineAssetWallet } from "@/api/queries"; +import { EngineTransferPowerDown } from "@/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-power-down"; +import { EngineTransferConfirmation } from "@/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-confirmation"; +import { EngineTransferSign } from "@/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-sign"; +import { EngineTransferSuccess } from "@/app/(dynamicPages)/profile/[username]/engine/_components/engine-transfer/engine-transfer-success"; interface Props { onHide: () => void; @@ -27,6 +31,13 @@ export function EngineTransfer({ onHide, mode, asset, to: preTo }: Props) { [mode] ); + const reset = useCallback(() => { + setStep(1); + setTo(preTo ?? ""); + setAmount(""); + setMemo(""); + }, [preTo]); + return ( - {step === 1 && ( + {step === 1 && mode !== "unstake" && ( setStep(2)} + precision={precision} + /> + )} + {step === 1 && mode === "unstake" && ( + setStep(2)} + asset={asset} + /> + )} + {step === 2 && ( + setStep(1)} + onConfirm={() => setStep(3)} + to={to} + amount={amount} + asset={asset} + memo={memo} + mode={mode} + /> + )} + {step === 3 && ( + setStep(2)} + onNext={() => setStep(4)} + to={to} + /> + )} + {step === 4 && ( + )} diff --git a/src/app/(dynamicPages)/profile/[username]/engine/_mutations/index.ts b/src/app/(dynamicPages)/profile/[username]/engine/_mutations/index.ts new file mode 100644 index 000000000..4dba7dfee --- /dev/null +++ b/src/app/(dynamicPages)/profile/[username]/engine/_mutations/index.ts @@ -0,0 +1,3 @@ +export * from "./transfer-engine-by-key"; +export * from "./transfer-engine-by-keychain"; +export * from "./transfer-engine-by-hivesigner"; diff --git a/src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-engine-by-hivesigner.ts b/src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-engine-by-hivesigner.ts new file mode 100644 index 000000000..e8e59cd50 --- /dev/null +++ b/src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-engine-by-hivesigner.ts @@ -0,0 +1,31 @@ +import { useCallback } from "react"; +import { hotSign } from "@/utils"; +import { useGlobalStore } from "@/core/global-store"; +import { useTransferGetOperation } from "@/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-get-operation"; + +export function useTransferEngineByHivesigner(afterSign: () => void) { + const activeUser = useGlobalStore((s) => s.activeUser); + + const getOperation = useTransferGetOperation(); + + return useCallback( + (payload: { amount: string; mode: string; to: string; memo: string; asset: string }) => { + if (!activeUser) { + throw new Error("[HiveEngine][Transfer][Key] No active user"); + } + + const op = getOperation({ + from: activeUser.username, + ...payload + }); + + if (!op) { + throw new Error("[HiveEngine][Transfer][Key] No operation details"); + } + + hotSign("custom-json", op, `@${activeUser.username}/engine`); + afterSign(); + }, + [activeUser, afterSign, getOperation] + ); +} diff --git a/src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-engine-by-key.ts b/src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-engine-by-key.ts new file mode 100644 index 000000000..9eff66e7c --- /dev/null +++ b/src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-engine-by-key.ts @@ -0,0 +1,49 @@ +import { useMutation } from "@tanstack/react-query"; +import { PrivateKey } from "@hiveio/dhive"; +import { useGlobalStore } from "@/core/global-store"; +import { client as hiveClient } from "@/api/hive"; +import { formatError } from "@/api/operations"; +import { error } from "@/features/shared"; +import { useTransferGetOperation } from "@/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-get-operation"; + +export function useTransferEngineByKey(onSuccess: () => void) { + const activeUser = useGlobalStore((s) => s.activeUser); + const updateActiveUser = useGlobalStore((s) => s.updateActiveUser); + + const getOperation = useTransferGetOperation(); + + return useMutation({ + mutationKey: ["transfer-engine", "key"], + mutationFn: async ({ + key, + ...payload + }: { + amount: string; + mode: string; + key: PrivateKey; + to: string; + memo: string; + asset: string; + }) => { + if (!activeUser) { + throw new Error("[HiveEngine][Transfer][Key] No active user"); + } + + const op = getOperation({ + from: activeUser.username, + ...payload + }); + + if (!op) { + throw new Error("[HiveEngine][Transfer][Key] No operation details"); + } + + return hiveClient.broadcast.json(op, key); + }, + onSuccess: () => { + onSuccess(); + updateActiveUser(); + }, + onError: (e) => error(...formatError(e)) + }); +} diff --git a/src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-engine-by-keychain.ts b/src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-engine-by-keychain.ts new file mode 100644 index 000000000..7b49b5544 --- /dev/null +++ b/src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-engine-by-keychain.ts @@ -0,0 +1,50 @@ +import { useMutation } from "@tanstack/react-query"; +import { error } from "@/features/shared"; +import { formatError } from "@/api/operations"; +import { useGlobalStore } from "@/core/global-store"; +import { useTransferGetOperation } from "@/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-get-operation"; +import * as keychain from "@/utils/keychain"; + +export function useTransferEngineByKeychain(onSuccess: () => void) { + const activeUser = useGlobalStore((s) => s.activeUser); + const updateActiveUser = useGlobalStore((s) => s.updateActiveUser); + + const getOperation = useTransferGetOperation(); + + return useMutation({ + mutationKey: ["transfer-engine", "keychain"], + mutationFn: async (payload: { + amount: string; + mode: string; + to: string; + memo: string; + asset: string; + }) => { + if (!activeUser) { + throw new Error("[HiveEngine][Transfer][Key] No active user"); + } + + const op = getOperation({ + from: activeUser.username, + ...payload + }); + + if (!op) { + throw new Error("[HiveEngine][Transfer][Key] No operation details"); + } + + return keychain.customJson( + activeUser.username, + "ssc-mainnet-hive", + "Active", + op.json, + "Transfer" + ); + }, + onSuccess: () => { + onSuccess(); + updateActiveUser(); + }, + onError: (e) => error(...formatError(e)) + }); +} diff --git a/src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-get-operation.ts b/src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-get-operation.ts new file mode 100644 index 000000000..ea4526975 --- /dev/null +++ b/src/app/(dynamicPages)/profile/[username]/engine/_mutations/transfer-get-operation.ts @@ -0,0 +1,114 @@ +import { useCallback } from "react"; +import { CustomJsonOperation } from "@hiveio/dhive/lib/chain/operation"; + +export function useTransferGetOperation() { + return useCallback( + ({ + from, + amount, + memo, + mode, + to, + asset + }: { + from: string; + amount: string; + mode: string; + to: string; + memo: string; + asset: string; + }) => { + let op: CustomJsonOperation[1] | undefined = undefined; + switch (mode) { + case "transfer": { + op = { + id: "ssc-mainnet-hive", + json: JSON.stringify({ + contractName: "tokens", + contractAction: "transfer", + contractPayload: { + symbol: asset, + to, + quantity: amount.toString(), + memo + } + }), + required_auths: [from], + required_posting_auths: [] + }; + break; + } + case "delegate": { + op = { + id: "ssc-mainnet-hive", + json: JSON.stringify({ + contractName: "tokens", + contractAction: "delegate", + contractPayload: { + symbol: asset, + to, + quantity: amount.toString() + } + }), + required_auths: [from], + required_posting_auths: [] + }; + break; + } + case "undelegate": { + op = { + id: "ssc-mainnet-hive", + json: JSON.stringify({ + contractName: "tokens", + contractAction: "undelegate", + contractPayload: { + symbol: asset, + from: to, + quantity: amount.toString() + } + }), + required_auths: [from], + required_posting_auths: [] + }; + break; + } + case "stake": { + op = { + id: "ssc-mainnet-hive", + json: JSON.stringify({ + contractName: "tokens", + contractAction: "stake", + contractPayload: { + symbol: asset, + to, + quantity: amount.toString() + } + }), + required_auths: [from], + required_posting_auths: [] + }; + break; + } + case "unstake": { + op = { + id: "ssc-mainnet-hive", + json: JSON.stringify({ + contractName: "tokens", + contractAction: "stake", + contractPayload: { + symbol: asset, + to, + quantity: amount.toString() + } + }), + required_auths: [from], + required_posting_auths: [] + }; + break; + } + } + return op; + }, + [] + ); +} diff --git a/src/features/i18n/locales/en-US.json b/src/features/i18n/locales/en-US.json index e1a1db0da..cfcdf2f6b 100644 --- a/src/features/i18n/locales/en-US.json +++ b/src/features/i18n/locales/en-US.json @@ -1294,7 +1294,9 @@ "lock-title": "Lock liquidity", "unlock-title": "Unlock liquidity", "lock-summary": "Locking completed as {{amount}}", - "unlock-summary": "Unlocking completed as {{amount}}" + "unlock-summary": "Unlocking completed as {{amount}}", + "success-title": "Success", + "success-sub-title": "Transaction completed" }, "trx-common": { "insufficient-funds": "Insufficient funds",