@@ -20,8 +20,6 @@ import {
2020 EDDSAMethodTypes ,
2121 MPCRecoveryOptions ,
2222 MPCTx ,
23- MPCUnsignedTx ,
24- RecoveryTxRequest ,
2523 OvcInput ,
2624 OvcOutput ,
2725 Environments ,
@@ -35,7 +33,7 @@ import {
3533import { auditEddsaPrivateKey , getDerivationPath } from '@bitgo/sdk-lib-mpc' ;
3634import { BaseCoin as StaticsBaseCoin , coins } from '@bitgo/statics' ;
3735import { KeyPair as TonKeyPair } from './lib/keyPair' ;
38- import { TransactionBuilderFactory , Utils , TransferBuilder } from './lib' ;
36+ import { TransactionBuilderFactory , Utils , TransferBuilder , TokenTransferBuilder , TransactionBuilder } from './lib' ;
3937import { getFeeEstimate } from './lib/utils' ;
4038
4139export interface TonParseTransactionOptions extends ParseTransactionOptions {
@@ -273,7 +271,9 @@ export class Ton extends BaseCoin {
273271 return new TransactionBuilderFactory ( coins . get ( this . getChain ( ) ) ) ;
274272 }
275273
276- async recover ( params : MPCRecoveryOptions ) : Promise < MPCTx | MPCSweepTxs > {
274+ async recover (
275+ params : MPCRecoveryOptions & { jettonMaster ?: string ; senderJettonAddress ?: string }
276+ ) : Promise < MPCTx | MPCSweepTxs > {
277277 if ( ! params . bitgoKey ) {
278278 throw new Error ( 'missing bitgoKey' ) ;
279279 }
@@ -295,50 +295,106 @@ export class Ton extends BaseCoin {
295295 const accountId = MPC . deriveUnhardened ( bitgoKey , currPath ) . slice ( 0 , 64 ) ;
296296 const senderAddr = await Utils . default . getAddressFromPublicKey ( accountId ) ;
297297 const balance = await tonweb . getBalance ( senderAddr ) ;
298- if ( new BigNumber ( balance ) . isEqualTo ( 0 ) ) {
299- throw Error ( 'Did not find address with funds to recover' ) ;
298+
299+ const jettonBalances : { minterAddress ?: string ; walletAddress : string ; balance : string } [ ] = [ ] ;
300+ if ( params . senderJettonAddress ) {
301+ try {
302+ const jettonWalletData = await tonweb . provider . call ( params . senderJettonAddress , 'get_wallet_data' ) ;
303+ const jettonBalance = jettonWalletData . stack [ 0 ] [ 1 ] ;
304+ if ( jettonBalance && new BigNumber ( jettonBalance ) . gt ( 0 ) ) {
305+ jettonBalances . push ( {
306+ walletAddress : params . senderJettonAddress ,
307+ balance : jettonBalance ,
308+ } ) ;
309+ }
310+ } catch ( e ) {
311+ throw new Error ( `Failed to query jetton balance for address ${ params . senderJettonAddress } : ${ e . message } ` ) ;
312+ }
300313 }
301314
302315 const WalletClass = tonweb . wallet . all [ 'v4R2' ] ;
303316 const wallet = new WalletClass ( tonweb . provider , {
304317 publicKey : tonweb . utils . hexToBytes ( accountId ) ,
305318 wc : 0 ,
306319 } ) ;
307- let seqno = await wallet . methods . seqno ( ) . call ( ) ;
308- if ( seqno === null ) {
309- seqno = 0 ;
310- }
320+ const seqnoResult = await wallet . methods . seqno ( ) . call ( ) ;
321+ const seqno : number = seqnoResult !== null && seqnoResult !== undefined ? seqnoResult : 0 ;
311322
312- const feeEstimate = await getFeeEstimate ( wallet , params . recoveryDestination , balance , seqno as number ) ;
323+ const factory = this . getBuilder ( ) ;
324+ const expireAt = Math . floor ( Date . now ( ) / 1e3 ) + 60 * 60 * 24 * 7 ;
325+
326+ let txBuilder : TransactionBuilder ;
327+ let unsignedTransaction : any ;
328+ let feeEstimate : number ;
329+ let transactionType : 'ton' | 'jetton' ;
330+
331+ if ( ( params . jettonMaster || params . senderJettonAddress ) && jettonBalances . length > 0 ) {
332+ const jettonInfo = jettonBalances [ 0 ] ;
333+ const tonAmount = '50000000' ;
334+ const forwardTonAmount = '1' ;
335+
336+ const totalRequiredTon = new BigNumber ( tonAmount ) . plus ( new BigNumber ( forwardTonAmount ) ) ;
337+ if ( new BigNumber ( balance ) . lt ( totalRequiredTon ) ) {
338+ throw new Error (
339+ `Insufficient TON balance for jetton transfer. Required: ${ totalRequiredTon . toString ( ) } nanoTON, Available: ${ balance } `
340+ ) ;
341+ }
313342
314- const totalFeeEstimate = Math . round (
315- ( feeEstimate . source_fees . in_fwd_fee +
316- feeEstimate . source_fees . storage_fee +
317- feeEstimate . source_fees . gas_fee +
318- feeEstimate . source_fees . fwd_fee ) *
319- 1.5
320- ) ;
343+ txBuilder = factory
344+ . getTokenTransferBuilder ( )
345+ . sender ( senderAddr )
346+ . sequenceNumber ( seqno )
347+ . publicKey ( accountId )
348+ . expireTime ( expireAt ) ;
349+
350+ ( txBuilder as TokenTransferBuilder ) . recipient (
351+ params . recoveryDestination ,
352+ jettonInfo . walletAddress ,
353+ tonAmount ,
354+ jettonInfo . balance ,
355+ forwardTonAmount
356+ ) ;
321357
322- if ( new BigNumber ( totalFeeEstimate ) . gt ( balance ) ) {
323- throw Error ( 'Did not find address with funds to recover' ) ;
324- }
358+ unsignedTransaction = await txBuilder . build ( ) ;
359+ feeEstimate = parseInt ( tonAmount , 10 ) ;
360+ transactionType = 'jetton' ;
361+ } else {
362+ if ( new BigNumber ( balance ) . isEqualTo ( 0 ) ) {
363+ throw Error ( 'Did not find address with TON balance to recover' ) ;
364+ }
325365
326- const factory = this . getBuilder ( ) ;
327- const expireAt = Math . floor ( Date . now ( ) / 1e3 ) + 60 * 60 * 24 * 7 ; // 7 days
328-
329- const txBuilder = factory
330- . getTransferBuilder ( )
331- . sender ( senderAddr )
332- . sequenceNumber ( seqno as number )
333- . publicKey ( accountId )
334- . expireTime ( expireAt ) ;
335-
336- ( txBuilder as TransferBuilder ) . send ( {
337- address : params . recoveryDestination ,
338- amount : new BigNumber ( balance ) . minus ( new BigNumber ( totalFeeEstimate ) ) . toString ( ) ,
339- } ) ;
366+ const tonFeeEstimate = await getFeeEstimate ( wallet , params . recoveryDestination , balance , seqno as number ) ;
367+
368+ const totalFeeEstimate = Math . round (
369+ ( tonFeeEstimate . source_fees . in_fwd_fee +
370+ tonFeeEstimate . source_fees . storage_fee +
371+ tonFeeEstimate . source_fees . gas_fee +
372+ tonFeeEstimate . source_fees . fwd_fee ) *
373+ 1.5
374+ ) ;
340375
341- const unsignedTransaction = await txBuilder . build ( ) ;
376+ if ( new BigNumber ( totalFeeEstimate ) . gte ( balance ) ) {
377+ throw new Error (
378+ `Insufficient TON balance for transaction. Required: ${ totalFeeEstimate } nanoTON, Available: ${ balance } `
379+ ) ;
380+ }
381+
382+ txBuilder = factory
383+ . getTransferBuilder ( )
384+ . sender ( senderAddr )
385+ . sequenceNumber ( seqno )
386+ . publicKey ( accountId )
387+ . expireTime ( expireAt ) ;
388+
389+ ( txBuilder as TransferBuilder ) . send ( {
390+ address : params . recoveryDestination ,
391+ amount : new BigNumber ( balance ) . minus ( new BigNumber ( totalFeeEstimate ) ) . toString ( ) ,
392+ } ) ;
393+
394+ unsignedTransaction = await txBuilder . build ( ) ;
395+ feeEstimate = totalFeeEstimate ;
396+ transactionType = 'ton' ;
397+ }
342398
343399 if ( ! isUnsignedSweep ) {
344400 if ( ! params . userKey ) {
@@ -389,31 +445,33 @@ export class Ton extends BaseCoin {
389445 txBuilder . addSignature ( publicKeyObj as PublicKey , signatureHex ) ;
390446 }
391447
392- const completedTransaction = await txBuilder . build ( ) ;
393- const serializedTx = completedTransaction . toBroadcastFormat ( ) ;
394448 const walletCoin = this . getChain ( ) ;
395-
396- const inputs : OvcInput [ ] = [ ] ;
397- for ( const input of completedTransaction . inputs ) {
398- inputs . push ( {
399- address : input . address ,
400- valueString : input . value ,
401- value : new BigNumber ( input . value ) . toNumber ( ) ,
402- } ) ;
403- }
404- const outputs : OvcOutput [ ] = [ ] ;
405- for ( const output of completedTransaction . outputs ) {
406- outputs . push ( {
407- address : output . address ,
408- valueString : output . value ,
409- coinName : output . coin ,
410- } ) ;
411- }
412- const spendAmount = completedTransaction . inputs . length === 1 ? completedTransaction . inputs [ 0 ] . value : 0 ;
413- const parsedTx = { inputs : inputs , outputs : outputs , spendAmount : spendAmount , type : '' } ;
414- const feeInfo = { fee : totalFeeEstimate , feeString : totalFeeEstimate . toString ( ) } ;
415449 const coinSpecific = { commonKeychain : bitgoKey } ;
450+
416451 if ( isUnsignedSweep ) {
452+ const completedTransaction = await txBuilder . build ( ) ;
453+ const serializedTx = completedTransaction . toBroadcastFormat ( ) ;
454+
455+ const inputs : OvcInput [ ] = [ ] ;
456+ for ( const input of completedTransaction . inputs ) {
457+ inputs . push ( {
458+ address : input . address ,
459+ valueString : input . value ,
460+ value : new BigNumber ( input . value ) . toNumber ( ) ,
461+ } ) ;
462+ }
463+ const outputs : OvcOutput [ ] = [ ] ;
464+ for ( const output of completedTransaction . outputs ) {
465+ outputs . push ( {
466+ address : output . address ,
467+ valueString : output . value ,
468+ coinName : output . coin ,
469+ } ) ;
470+ }
471+ const spendAmount = completedTransaction . inputs . length === 1 ? completedTransaction . inputs [ 0 ] . value : 0 ;
472+ const parsedTx = { inputs : inputs , outputs : outputs , spendAmount : spendAmount , type : transactionType } ;
473+ const feeInfo = { fee : feeEstimate , feeString : feeEstimate . toString ( ) } ;
474+
417475 const transaction : MPCTx = {
418476 serializedTx : serializedTx ,
419477 scanIndex : index ,
@@ -424,16 +482,13 @@ export class Ton extends BaseCoin {
424482 feeInfo : feeInfo ,
425483 coinSpecific : coinSpecific ,
426484 } ;
427- const unsignedTx : MPCUnsignedTx = { unsignedTx : transaction , signatureShares : [ ] } ;
428- const transactions : MPCUnsignedTx [ ] = [ unsignedTx ] ;
429- const txRequest : RecoveryTxRequest = {
430- transactions : transactions ,
431- walletCoin : walletCoin ,
432- } ;
433- const txRequests : MPCSweepTxs = { txRequests : [ txRequest ] } ;
434- return txRequests ;
485+
486+ return transaction ;
435487 }
436488
489+ const completedTransaction = await txBuilder . build ( ) ;
490+ const serializedTx = completedTransaction . toBroadcastFormat ( ) ;
491+
437492 const transaction : MPCTx = {
438493 serializedTx : serializedTx ,
439494 scanIndex : index ,
0 commit comments