Skip to content

Commit 421d6cc

Browse files
authored
Merge pull request #709 from BitGo/feat/xrp-account-delete-recovery
feat(xrp): add AccountDelete feature
2 parents 31351e9 + 76b61b1 commit 421d6cc

6 files changed

Lines changed: 169 additions & 0 deletions

File tree

electron/main/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { TrxConsolidationRecoveryOptions } from '../types';
22
import EthereumCommon from '@ethereumjs/common';
33

4+
// Allow self-signed / intermediate-CA certs when running in dev mode.
5+
// This is needed for testnet rippled nodes (s.altnet.rippletest.net) whose
6+
// certificate chain is not in Node's default CA bundle.
7+
if (process.env.NODE_ENV !== 'production') {
8+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
9+
}
10+
411
process.env.DIST_ELECTRON = join(__dirname, '../..');
512
process.env.DIST = join(process.env.DIST_ELECTRON, '../dist');
613
process.env.PUBLIC = app.isPackaged

src/containers/BuildUnsignedSweepCoin/BuildUnsignedSweepCoin.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,69 @@ function Form() {
391391
);
392392
case 'xrp':
393393
case 'txrp':
394+
return (
395+
<RippleForm
396+
key={coin}
397+
onSubmit={async (values, { setSubmitting }) => {
398+
setAlert(undefined);
399+
setSubmitting(true);
400+
try {
401+
await window.commands.setBitGoEnvironment(bitGoEnvironment, coin);
402+
const chainData = await window.queries.getChain(coin);
403+
const recoverData = await window.commands.recover(coin, {
404+
...(await updateKeysFromIds(coin, values)),
405+
bitgoKey: '',
406+
ignoreAddressTypes: [],
407+
reserveWithdrawal: values.reserveWithdrawal,
408+
});
409+
assert(
410+
isRecoveryTransaction(recoverData),
411+
'Fully-signed recovery transaction not detected.'
412+
);
413+
414+
const showSaveDialogData = await window.commands.showSaveDialog({
415+
filters: [
416+
{
417+
name: 'Custom File Type',
418+
extensions: ['json'],
419+
},
420+
],
421+
defaultPath: `~/${chainData}-unsigned-sweep-${Date.now()}.json`,
422+
});
423+
424+
if (!showSaveDialogData.filePath) {
425+
throw new Error('No file path selected');
426+
}
427+
428+
await window.commands.writeFile(
429+
showSaveDialogData.filePath,
430+
JSON.stringify(
431+
includePubsInUnsignedSweep
432+
? {
433+
...recoverData,
434+
...(await includePubsFor(coin, values)),
435+
}
436+
: recoverData,
437+
null,
438+
2
439+
),
440+
{ encoding: 'utf-8' }
441+
);
442+
443+
navigate(
444+
`/${bitGoEnvironment}/build-unsigned-sweep/${coin}/success`
445+
);
446+
} catch (err) {
447+
if (err instanceof Error) {
448+
setAlert(err.message);
449+
} else {
450+
console.error(err);
451+
}
452+
setSubmitting(false);
453+
}
454+
}}
455+
/>
456+
);
394457
case 'xlm':
395458
case 'txlm':
396459
case 'eos':

src/containers/BuildUnsignedSweepCoin/RippleForm.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const validationSchema = Yup.object({
1010
rootAddress: Yup.string().required(),
1111
userKey: Yup.string().required(),
1212
userKeyId: Yup.string(),
13+
reserveWithdrawal: Yup.boolean(),
1314
}).required();
1415

1516
export type RippleFormProps = {
@@ -31,6 +32,7 @@ export function RippleForm({ onSubmit }: RippleFormProps) {
3132
rootAddress: '',
3233
userKey: '',
3334
userKeyId: '',
35+
reserveWithdrawal: false,
3436
},
3537
validationSchema,
3638
});
@@ -89,6 +91,26 @@ export function RippleForm({ onSubmit }: RippleFormProps) {
8991
Width="fill"
9092
/>
9193
</div>
94+
<div className="tw-mb-4 tw-flex tw-items-start tw-gap-2">
95+
<input
96+
type="checkbox"
97+
id="reserveWithdrawal"
98+
name="reserveWithdrawal"
99+
checked={formik.values.reserveWithdrawal}
100+
onChange={formik.handleChange}
101+
className="tw-mt-1"
102+
/>
103+
<label htmlFor="reserveWithdrawal" className="tw-text-sm">
104+
<span className="tw-font-semibold">Withdraw full balance including reserve (AccountDelete)</span>
105+
<br />
106+
<span className="tw-text-gray-500">
107+
Permanently deletes the XRP account and sends the entire balance — including the 10 XRP
108+
base reserve — to the destination. The account cannot be reused afterwards. Requires: no
109+
trustlines with non-zero balances, no open offers/escrows/checks, and the account must be
110+
at least 256 ledgers old. A 2 XRP deletion fee is charged by the network.
111+
</span>
112+
</label>
113+
</div>
92114
<div className="tw-flex tw-flex-col-reverse sm:tw-justify-between sm:tw-flex-row tw-gap-1 tw-mt-4">
93115
<Button Tag={Link} to="/" Variant="secondary" Width="hug">
94116
Cancel

src/containers/NonBitGoRecoveryCoin/NonBitGoRecoveryCoin.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,60 @@ function Form() {
699699
);
700700
case 'xrp':
701701
case 'txrp':
702+
return (
703+
<RippleForm
704+
key={coin}
705+
onSubmit={async (values, { setSubmitting }) => {
706+
setAlert(undefined);
707+
setSubmitting(true);
708+
try {
709+
await window.commands.setBitGoEnvironment(bitGoEnvironment, coin);
710+
const chainData = await window.queries.getChain(coin);
711+
const recoverData = await window.commands.recover(coin, {
712+
...values,
713+
bitgoKey: '',
714+
ignoreAddressTypes: [],
715+
reserveWithdrawal: values.reserveWithdrawal,
716+
});
717+
assert(
718+
isRecoveryTransaction(recoverData),
719+
'Fully-signed recovery transaction not detected.'
720+
);
721+
722+
const showSaveDialogData = await window.commands.showSaveDialog({
723+
filters: [
724+
{
725+
name: 'Custom File Type',
726+
extensions: ['json'],
727+
},
728+
],
729+
defaultPath: `~/${chainData}-recovery-${Date.now()}.json`,
730+
});
731+
732+
if (!showSaveDialogData.filePath) {
733+
throw new Error('No file path selected');
734+
}
735+
736+
await window.commands.writeFile(
737+
showSaveDialogData.filePath,
738+
JSON.stringify(recoverData, null, 2),
739+
{ encoding: 'utf-8' }
740+
);
741+
742+
navigate(
743+
`/${bitGoEnvironment}/non-bitgo-recovery/${coin}/success`
744+
);
745+
} catch (err) {
746+
if (err instanceof Error) {
747+
setAlert(err.message);
748+
} else {
749+
console.error(err);
750+
}
751+
setSubmitting(false);
752+
}
753+
}}
754+
/>
755+
);
702756
case 'xlm':
703757
case 'txlm':
704758
case 'eos':

src/containers/NonBitGoRecoveryCoin/RippleForm.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const validationSchema = Yup.object({
1818
rootAddress: Yup.string().required(),
1919
walletPassphrase: Yup.string().required(),
2020
recoveryDestination: Yup.string().required(),
21+
reserveWithdrawal: Yup.boolean(),
2122
}).required();
2223

2324
export type RippleFormProps = {
@@ -39,6 +40,7 @@ export function RippleForm({ onSubmit }: RippleFormProps) {
3940
walletPassphrase: '',
4041
recoveryDestination: '',
4142
krsProvider: '',
43+
reserveWithdrawal: false,
4244
},
4345
validationSchema,
4446
});
@@ -109,6 +111,26 @@ export function RippleForm({ onSubmit }: RippleFormProps) {
109111
Width="fill"
110112
/>
111113
</div>
114+
<div className="tw-mb-4 tw-flex tw-items-start tw-gap-2">
115+
<input
116+
type="checkbox"
117+
id="reserveWithdrawal"
118+
name="reserveWithdrawal"
119+
checked={formik.values.reserveWithdrawal}
120+
onChange={formik.handleChange}
121+
className="tw-mt-1"
122+
/>
123+
<label htmlFor="reserveWithdrawal" className="tw-text-sm">
124+
<span className="tw-font-semibold">Withdraw full balance including reserve (AccountDelete)</span>
125+
<br />
126+
<span className="tw-text-gray-500">
127+
Permanently deletes the XRP account and sends the entire balance — including the 10 XRP
128+
base reserve — to the destination. The account cannot be reused afterwards. Requires: no
129+
trustlines with non-zero balances, no open offers/escrows/checks, and the account must be
130+
at least 256 ledgers old. A 2 XRP deletion fee is charged by the network.
131+
</span>
132+
</label>
133+
</div>
112134
<div className="tw-flex tw-flex-col-reverse sm:tw-justify-between sm:tw-flex-row tw-gap-1 tw-mt-4">
113135
<Button Tag={Link} to="/" Variant="secondary" Width="hug">
114136
Cancel

src/preload.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ type Commands = {
125125
isUnsignedSweep?: boolean;
126126
issuerAddress?: string; // eg. xrpl token
127127
currencyCode?: string; // eg. xrpl token
128+
reserveWithdrawal?: boolean; // xrp: delete the account and recover the full balance including reserve
128129
tokenId?: string; // eg. hbar token
129130
contractId?: string; // eg. stacks sip10 token
130131
programId?: string; // eg. solana spl 2022 token

0 commit comments

Comments
 (0)