Skip to content

Commit

Permalink
feat: add WRW support for hbarToken
Browse files Browse the repository at this point in the history
  • Loading branch information
abhishekagrawal080 committed Feb 7, 2025
1 parent 480c0a2 commit 7debe34
Show file tree
Hide file tree
Showing 7 changed files with 481 additions and 36 deletions.
5 changes: 4 additions & 1 deletion electron/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import fs from 'node:fs/promises';
import { release } from 'os';
import { join } from 'path';
import * as ecc from 'tiny-secp256k1';
import { Hbar, Thbar } from '@bitgo/sdk-coin-hbar';
import { Hbar, Thbar , HbarToken} from '@bitgo/sdk-coin-hbar';
import { Algo, Talgo } from '@bitgo/sdk-coin-algo';
import { EthLikeCoin, TethLikeCoin } from '@bitgo/sdk-coin-ethlike';
import { Sui, Tsui } from '@bitgo/sdk-coin-sui';
Expand Down Expand Up @@ -181,6 +181,9 @@ TrxToken.createTokenConstructors().forEach(({ name, coinConstructor }) => {
XrpToken.createTokenConstructors().forEach(({ name, coinConstructor }) => {
sdk.register(name, coinConstructor);
});
HbarToken.createTokenConstructors().forEach(({ name, coinConstructor }) => {
sdk.register(name, coinConstructor);
});

function handleSdkError(e: unknown): string {
if (typeof e === 'string' && e !== null) {
Expand Down
136 changes: 101 additions & 35 deletions src/containers/BuildUnsignedSweepCoin/BuildUnsignedSweepCoin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { buildUnsignedSweepCoins, tokenParentCoins } from '~/helpers/config';
import { HederaForm } from './HederaForm';
import { AlgorandForm } from '~/containers/BuildUnsignedSweepCoin/AlgorandForm';
import { RippleTokenForm } from '~/containers/BuildUnsignedSweepCoin/RippleTokenForm';
import { HederaTokenForm } from '~/containers/BuildUnsignedSweepCoin/HederaTokenForm';

function Form() {
const { env, coin } = useParams<'env' | 'coin'>();
Expand Down Expand Up @@ -177,9 +178,9 @@ function Form() {
JSON.stringify(
includePubsInUnsignedSweep
? {
...recoverData,
...(await includePubsFor(coin, values)),
}
...recoverData,
...(await includePubsFor(coin, values)),
}
: recoverData,
null,
2
Expand Down Expand Up @@ -251,9 +252,9 @@ function Form() {
JSON.stringify(
includePubsInUnsignedSweep
? {
...recoverData,
...(await includePubsFor(coin, values)),
}
...recoverData,
...(await includePubsFor(coin, values)),
}
: recoverData,
null,
2
Expand Down Expand Up @@ -317,9 +318,9 @@ function Form() {
JSON.stringify(
includePubsInUnsignedSweep
? {
...recoverData,
...(await includePubsFor(coin, values)),
}
...recoverData,
...(await includePubsFor(coin, values)),
}
: recoverData,
null,
2
Expand Down Expand Up @@ -387,13 +388,13 @@ function Form() {
JSON.stringify(
includePubsInUnsignedSweep
? {
...recoverData,
...(await includePubsForToken(
values.tokenContractAddress.toLowerCase(),
coin,
values
)),
}
...recoverData,
...(await includePubsForToken(
values.tokenContractAddress.toLowerCase(),
coin,
values
)),
}
: recoverData,
null,
2
Expand Down Expand Up @@ -458,9 +459,9 @@ function Form() {
JSON.stringify(
includePubsInUnsignedSweep
? {
...recoverData,
...(await includePubsFor(coin, values)),
}
...recoverData,
...(await includePubsFor(coin, values)),
}
: recoverData,
null,
2
Expand Down Expand Up @@ -931,9 +932,9 @@ function Form() {
JSON.stringify(
includePubsInUnsignedSweep
? {
...recoverData,
...(await includePubsFor(coin, values)),
}
...recoverData,
...(await includePubsFor(coin, values)),
}
: recoverData,
null,
2
Expand Down Expand Up @@ -1034,13 +1035,13 @@ function Form() {
JSON.stringify(
includePubsInUnsignedSweep
? {
...recoverData,
...(await includePubsForToken(
values.tokenContractAddress.toLowerCase(),
coin,
values
)),
}
...recoverData,
...(await includePubsForToken(
values.tokenContractAddress.toLowerCase(),
coin,
values
)),
}
: recoverData,
null,
2
Expand Down Expand Up @@ -1377,9 +1378,74 @@ function Form() {
JSON.stringify(
includePubsInUnsignedSweep
? {
...recoverData,
...(await includePubsFor(coin, values)),
}
...recoverData,
...(await includePubsFor(coin, values)),
}
: recoverData,
null,
2
),
{ encoding: 'utf-8' }
);

navigate(
`/${bitGoEnvironment}/build-unsigned-sweep/${coin}/success`
);
} catch (err) {
if (err instanceof Error) {
setAlert(err.message);
} else {
console.error(err);
}
setSubmitting(false);
}
}}
/>
);
case 'hbarToken':
case 'thbarToken':
return (
<HederaTokenForm
key={coin}
onSubmit={async (values, { setSubmitting }) => {
setAlert(undefined);
setSubmitting(true);
try {
await window.commands.setBitGoEnvironment(bitGoEnvironment, coin);
const parentCoin = tokenParentCoins[coin];
const chainData = await window.queries.getChain(coin);
const recoverData = await window.commands.recover(parentCoin, {
...(await updateKeysFromIds(coin, values)),
bitgoKey: '',
ignoreAddressTypes: [],
});
assert(
isRecoveryTransaction(recoverData),
'Recovery transaction not detected.'
);

const showSaveDialogData = await window.commands.showSaveDialog({
filters: [
{
name: 'Custom File Type',
extensions: ['json'],
},
],
defaultPath: `~/${chainData}-unsigned-sweep-${Date.now()}.json`,
});

if (!showSaveDialogData.filePath) {
throw new Error('No file path selected');
}

await window.commands.writeFile(
showSaveDialogData.filePath,
JSON.stringify(
includePubsInUnsignedSweep
? {
...recoverData,
...(await includePubsFor(coin, values)),
}
: recoverData,
null,
2
Expand Down Expand Up @@ -1441,9 +1507,9 @@ function Form() {
JSON.stringify(
includePubsInUnsignedSweep
? {
...recoverData,
...(await includePubsFor(coin, values)),
}
...recoverData,
...(await includePubsFor(coin, values)),
}
: recoverData,
null,
2
Expand Down
151 changes: 151 additions & 0 deletions src/containers/BuildUnsignedSweepCoin/HederaTokenForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Form, FormikHelpers, FormikProvider, useFormik } from 'formik';
import { Link } from 'react-router-dom';
import * as Yup from 'yup';
import { Button, FormikTextfield } from '~/components';

const validationSchema = Yup.object({
backupKey: Yup.string().required(),
backupKeyId: Yup.string().optional(),
userKey: Yup.string().required(),
userKeyId: Yup.string().optional(),
recoveryDestination: Yup.string().required(),
rootAddress: Yup.string().required(),
tokenId: Yup.string().required(),
maxFee: Yup.number().optional(),
startTime: Yup.number()
.required()
.min(Date.now() / 1000, 'Start time must be in the future'),
nodeId: Yup.string().optional(),
}).required();

export type HederaTokenFormProps = {
onSubmit: (
values: HederaTokenFormValues,
formikHelpers: FormikHelpers<HederaTokenFormValues>
) => void | Promise<void>;
};

type HederaTokenFormValues = Yup.Asserts<typeof validationSchema>;

export function HederaTokenForm({ onSubmit }: HederaTokenFormProps) {
const formik = useFormik<HederaTokenFormValues>({
onSubmit,
initialValues: {
userKey: '',
userKeyId: '',
backupKey: '',
backupKeyId: '',
recoveryDestination: '',
rootAddress: '',
tokenId: '',
maxFee: undefined,
nodeId: undefined,
startTime: 0,
},
validationSchema,
});

return (
<FormikProvider value={formik}>
<Form>
<h4 className="tw-text-body tw-font-semibold tw-border-b-0.5 tw-border-solid tw-border-gray-700 tw-mb-4">
Self-Managed Cold Wallet
</h4>
<div className="tw-mb-4">
<FormikTextfield
HelperText="Your public user key (box A on your KeyCard)"
Label="Public User Key"
name="userKey"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="Your user key ID (check on your KeyCard). Note: most wallets don’t have this."
Label="User Key ID (Optional)"
name="userKeyId"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="Your public backup key (box B on your KeyCard)"
Label="Backup Public Key"
name="backupKey"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="Your backup key ID (check on your KeyCard). Note: most wallets don’t have this."
Label="Backup Key ID (Optional"
name="backupKeyId"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="The base address of the wallet (also known as root address)"
Label="Base Address"
name="rootAddress"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="The token id of the token you want to recover."
Label="Token Id"
name="tokenId"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="The recipient address for the recovery transaction. If a memo ID is required, append it to the address. For example: 0.0.1234?memoId=3"
Label="Destination Address"
name="recoveryDestination"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="Specify when the recovery transaction starts, using a future UNIX timestamp in seconds. Once started, the validity window lasts for 180 seconds."
Label="Validity Window Start Time"
name="startTime"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="The maximum fee that you’re willing to pay for the recovery transaction. Defaults to 10000000 tinybars (0.1 Hbar)"
Label="Maximum Fee in Base Units (Optional)"
name="maxFee"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="The account ID of your preferred consensus node. If empty, defaults to node account ID 0.0.3."
Label="Node Account ID (Optional)"
name="nodeId"
Width="fill"
/>
</div>
<div className="tw-flex tw-flex-col-reverse sm:tw-justify-between sm:tw-flex-row tw-gap-1 tw-mt-4">
<Button Tag={Link} to="/" Variant="secondary" Width="hug">
Cancel
</Button>
<Button
Variant="primary"
Width="hug"
type="submit"
Disabled={formik.isSubmitting}
disabled={formik.isSubmitting}
>
{formik.isSubmitting ? 'Building...' : 'Build Transaction'}
</Button>
</div>
</Form>
</FormikProvider>
);
}
Loading

0 comments on commit 7debe34

Please sign in to comment.