From 695b3107d6ee394f9e89becf7eb0b828dcfdbbb2 Mon Sep 17 00:00:00 2001 From: N V Rakesh Reddy Date: Fri, 22 Nov 2024 10:57:22 +0530 Subject: [PATCH] feat(xrp): xrpl token support for non-bitgo recovery and build unsigned sweep --- .../BuildUnsignedSweepCoin.tsx | 64 ++++++++ .../RippleTokenForm.tsx | 129 +++++++++++++++ .../NonBitGoRecoveryCoin.tsx | 55 +++++++ .../NonBitGoRecoveryCoin/RippleTokenForm.tsx | 149 ++++++++++++++++++ src/preload.d.ts | 2 + 5 files changed, 399 insertions(+) create mode 100644 src/containers/BuildUnsignedSweepCoin/RippleTokenForm.tsx create mode 100644 src/containers/NonBitGoRecoveryCoin/RippleTokenForm.tsx diff --git a/src/containers/BuildUnsignedSweepCoin/BuildUnsignedSweepCoin.tsx b/src/containers/BuildUnsignedSweepCoin/BuildUnsignedSweepCoin.tsx index 00da3d67..4f3a40f9 100644 --- a/src/containers/BuildUnsignedSweepCoin/BuildUnsignedSweepCoin.tsx +++ b/src/containers/BuildUnsignedSweepCoin/BuildUnsignedSweepCoin.tsx @@ -36,6 +36,7 @@ import { BackToHomeHelperText } from '~/components/BackToHomeHelperText'; import { buildUnsignedSweepCoins, tokenParentCoins } from '~/helpers/config'; import { HederaForm } from './HederaForm'; import { AlgorandForm } from '~/containers/BuildUnsignedSweepCoin/AlgorandForm'; +import { RippleTokenForm } from '~/containers/BuildUnsignedSweepCoin/RippleTokenForm'; function Form() { const { env, coin } = useParams<'env' | 'coin'>(); @@ -461,6 +462,69 @@ function Form() { { 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 'txrpToken': + return ( + { + setAlert(undefined); + setSubmitting(true); + try { + await window.commands.setBitGoEnvironment(bitGoEnvironment, coin); + const chainData = await window.queries.getChain(coin); + const recoverData = await window.commands.recover(coin, { + ...(await updateKeysFromIds(coin, values)), + bitgoKey: '', + ignoreAddressTypes: [], + }); + assert( + isRecoveryTransaction(recoverData), + 'Fully-signed 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 + ), + { encoding: 'utf-8' } + ); + navigate( `/${bitGoEnvironment}/build-unsigned-sweep/${coin}/success` ); diff --git a/src/containers/BuildUnsignedSweepCoin/RippleTokenForm.tsx b/src/containers/BuildUnsignedSweepCoin/RippleTokenForm.tsx new file mode 100644 index 00000000..92c16431 --- /dev/null +++ b/src/containers/BuildUnsignedSweepCoin/RippleTokenForm.tsx @@ -0,0 +1,129 @@ +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(), + recoveryDestination: Yup.string().required(), + rootAddress: Yup.string().required(), + userKey: Yup.string().required(), + userKeyId: Yup.string(), + issuerAddress: Yup.string().required(), + currencyCode: Yup.string().required(), +}).required(); + +export type RippleTokenFormProps = { + onSubmit: ( + values: RippleTokenFormValues, + formikHelpers: FormikHelpers + ) => void | Promise; +}; + +type RippleTokenFormValues = Yup.Asserts; + +export function RippleTokenForm({ onSubmit }: RippleTokenFormProps) { + const formik = useFormik({ + onSubmit, + initialValues: { + backupKey: '', + backupKeyId: '', + recoveryDestination: '', + rootAddress: '', + userKey: '', + userKeyId: '', + issuerAddress: '', + currencyCode: '', + }, + validationSchema, + }); + + return ( + +
+

+ Self-managed cold wallet details +

+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+
+ ); +} diff --git a/src/containers/NonBitGoRecoveryCoin/NonBitGoRecoveryCoin.tsx b/src/containers/NonBitGoRecoveryCoin/NonBitGoRecoveryCoin.tsx index 3f12b862..f4fc22eb 100644 --- a/src/containers/NonBitGoRecoveryCoin/NonBitGoRecoveryCoin.tsx +++ b/src/containers/NonBitGoRecoveryCoin/NonBitGoRecoveryCoin.tsx @@ -32,6 +32,7 @@ import { TronTokenForm } from './TronTokenForm'; import { AvalancheCTokenForm } from './AvalancheCTokenForm'; import { HederaForm } from './HederaForm'; import { AlgorandForm } from '~/containers/NonBitGoRecoveryCoin/AlgorandForm'; +import { RippleTokenForm } from '~/containers/NonBitGoRecoveryCoin/RippleTokenForm'; function Form() { const { env, coin } = useParams<'env' | 'coin'>(); @@ -652,6 +653,60 @@ function Form() { }} /> ); + case 'txrpToken': + return ( + { + setAlert(undefined); + setSubmitting(true); + try { + await window.commands.setBitGoEnvironment(bitGoEnvironment, coin); + const chainData = await window.queries.getChain(coin); + const recoverData = await window.commands.recover(coin, { + ...values, + bitgoKey: '', + ignoreAddressTypes: [], + }); + assert( + isRecoveryTransaction(recoverData), + 'Fully-signed recovery transaction not detected.' + ); + + const showSaveDialogData = await window.commands.showSaveDialog({ + filters: [ + { + name: 'Custom File Type', + extensions: ['json'], + }, + ], + defaultPath: `~/${chainData}-recovery-${Date.now()}.json`, + }); + + if (!showSaveDialogData.filePath) { + throw new Error('No file path selected'); + } + + await window.commands.writeFile( + showSaveDialogData.filePath, + JSON.stringify(recoverData, null, 2), + { encoding: 'utf-8' } + ); + + navigate( + `/${bitGoEnvironment}/non-bitgo-recovery/${coin}/success` + ); + } catch (err) { + if (err instanceof Error) { + setAlert(err.message); + } else { + console.error(err); + } + setSubmitting(false); + } + }} + /> + ); case 'bch': return ( + ) => void | Promise; +}; + +type RippleTokenFormValues = Yup.Asserts; + +export function RippleTokenForm({ onSubmit }: RippleTokenFormProps) { + const formik = useFormik({ + onSubmit, + initialValues: { + userKey: '', + backupKey: '', + rootAddress: '', + walletPassphrase: '', + issuerAddress: '', + currencyCode: '', + recoveryDestination: '', + krsProvider: '', + }, + validationSchema, + }); + + const backupKeyHelperText = + formik.values.krsProvider === '' + ? 'Your encrypted backup key, as found on your recovery KeyCard.' + : 'The backup public key for the wallet, as found on your recovery KeyCard.'; + + return ( + +
+

+ Self-managed hot wallet details +

+
+ + + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+
+ ); +} diff --git a/src/preload.d.ts b/src/preload.d.ts index 7e074c23..7409b91b 100644 --- a/src/preload.d.ts +++ b/src/preload.d.ts @@ -104,6 +104,8 @@ type Commands = { seed?: string; common?: EthLikeCommon.default; ethCommonParams?: EvmCcrNonBitgoCoinConfigType | undefined; + issuerAddress?: string, // eg. xrpl token + currencyCode?: string, // eg. xrpl token } ): Promise; wrongChainRecover(