@@ -329,14 +329,24 @@ export class Xrp extends BaseCoin {
329329 params : [
330330 {
331331 account : params . rootAddress ,
332- strict : true ,
333332 ledger_index : 'current' ,
334333 queue : true ,
334+ strict : true ,
335335 signer_lists : true ,
336336 } ,
337337 ] ,
338338 } ;
339339
340+ const accountLinesParams = {
341+ method : 'account_lines' ,
342+ params : [
343+ {
344+ account : params . rootAddress ,
345+ ledger_index : 'validated' ,
346+ } ,
347+ ] ,
348+ } ;
349+
340350 if ( isKrsRecovery ) {
341351 checkKrsProvider ( this , params . krsProvider ) ;
342352 }
@@ -348,10 +358,11 @@ export class Xrp extends BaseCoin {
348358
349359 const keys = getBip32Keys ( this . bitgo , params , { requireBitGoXpub : false } ) ;
350360
351- const { addressDetails, feeDetails, serverDetails } = await promiseProps ( {
361+ const { addressDetails, feeDetails, serverDetails, accountLines } = await promiseProps ( {
352362 addressDetails : this . bitgo . post ( rippledUrl ) . send ( accountInfoParams ) ,
353363 feeDetails : this . bitgo . post ( rippledUrl ) . send ( { method : 'fee' } ) ,
354364 serverDetails : this . bitgo . post ( rippledUrl ) . send ( { method : 'server_info' } ) ,
365+ accountLines : this . bitgo . post ( rippledUrl ) . send ( accountLinesParams ) ,
355366 } ) ;
356367
357368 const openLedgerFee = new BigNumber ( feeDetails . body . result . drops . open_ledger_fee ) ;
@@ -452,6 +463,25 @@ export class Xrp extends BaseCoin {
452463 ) ;
453464 }
454465
466+ const tokenName = params ?. tokenName ;
467+ if ( ! ! tokenName ) {
468+ const tokenParams = {
469+ destinationAddress,
470+ destinationTag,
471+ recoverableBalance,
472+ currentLedger,
473+ openLedgerFee,
474+ sequenceId,
475+ accountLines,
476+ keys,
477+ isKrsRecovery,
478+ userAddress,
479+ backupAddress,
480+ } ;
481+
482+ return this . recoverXrpToken ( params , tokenName , tokenParams ) ;
483+ }
484+
455485 const transaction = {
456486 TransactionType : 'Payment' ,
457487 Account : params . rootAddress , // source address
@@ -504,6 +534,82 @@ export class Xrp extends BaseCoin {
504534 return transactionExplanation ;
505535 }
506536
537+ public async recoverXrpToken ( params , tokenName , tokenParams ) {
538+ const { currency, issuer } = utils . getXrpCurrencyFromTokenName ( tokenName ) ;
539+
540+ // const accountLines = JSON.parse(tokenParams.accountLines);
541+ // const lines = accountLines.body.result.lines;
542+ const lines = tokenParams . accountLines . body . result . lines ;
543+
544+ let amount ;
545+ for ( const line of lines ) {
546+ if ( line . currency === currency && line . account === issuer ) {
547+ amount = line . balance ;
548+ break ;
549+ }
550+ }
551+
552+ if ( amount === undefined ) {
553+ throw new Error ( `Does not have Trustline with ${ issuer } ` ) ;
554+ }
555+ if ( amount === '0' ) {
556+ throw new Error ( `Does not have funds to recover` ) ;
557+ }
558+
559+ const FLAG_VALUE = 2147483648 ;
560+
561+ const transaction = {
562+ TransactionType : 'Payment' ,
563+ Amount : {
564+ value : amount ,
565+ currency,
566+ issuer,
567+ } , // source address
568+ Destination : tokenParams . destinationAddress ,
569+ DestinationTag : tokenParams . destinationTag ,
570+ Account : params . rootAddress ,
571+ Flags : FLAG_VALUE ,
572+ LastLedgerSequence : tokenParams . currentLedger + 1000000 , // give it 1 million ledgers' time (~1 month, suitable for KRS)
573+ Fee : tokenParams . openLedgerFee . times ( 3 ) . toFixed ( 0 ) , // the factor three is for the multisigning
574+ Sequence : tokenParams . sequenceId ,
575+ } ;
576+ const txJSON : string = JSON . stringify ( transaction ) ;
577+
578+ const { keys, isKrsRecovery, userAddress, backupAddress } = tokenParams ;
579+
580+ if ( ! keys [ 0 ] . privateKey ) {
581+ throw new Error ( `userKey is not a private key` ) ;
582+ }
583+
584+ const userKey = keys [ 0 ] . privateKey . toString ( 'hex' ) ;
585+ const userSignature = ripple . signWithPrivateKey ( txJSON , userKey , { signAs : userAddress } ) ;
586+
587+ let signedTransaction : string ;
588+
589+ if ( isKrsRecovery ) {
590+ signedTransaction = userSignature . signedTransaction ;
591+ } else {
592+ if ( ! keys [ 1 ] . privateKey ) {
593+ throw new Error ( `backupKey is not a private key` ) ;
594+ }
595+ const backupKey = keys [ 1 ] . privateKey . toString ( 'hex' ) ;
596+ const backupSignature = ripple . signWithPrivateKey ( txJSON , backupKey , { signAs : backupAddress } ) ;
597+ signedTransaction = ripple . multisign ( [ userSignature . signedTransaction , backupSignature . signedTransaction ] ) ;
598+ }
599+
600+ const transactionExplanation : RecoveryInfo = ( await this . explainTransaction ( {
601+ txHex : signedTransaction ,
602+ } ) ) as RecoveryInfo ;
603+
604+ transactionExplanation . txHex = signedTransaction ;
605+
606+ if ( isKrsRecovery ) {
607+ transactionExplanation . backupKey = params . backupKey ;
608+ transactionExplanation . coin = this . getChain ( ) ;
609+ }
610+ return transactionExplanation ;
611+ }
612+
507613 /**
508614 * Generate a new keypair for this coin.
509615 * @param seed Seed from which the new keypair should be generated, otherwise a random seed is used
0 commit comments