@@ -620,6 +620,7 @@ describe('Hedera Hashgraph:', function () {
620620 const balance = '1000000000' ;
621621 const formatBalanceResponse = ( balance : string ) =>
622622 new BigNumber ( balance ) . dividedBy ( basecoin . getBaseFactor ( ) ) . toFixed ( 9 ) + ' ℏ' ;
623+ const tokenId = '0.0.13078' ;
623624
624625 describe ( 'Non-BitGo' , async function ( ) {
625626 const sandBox = Sinon . createSandbox ( ) ;
@@ -781,23 +782,144 @@ describe('Hedera Hashgraph:', function () {
781782 }
782783 ) ;
783784 } ) ;
785+
786+ it ( 'should build and sign the recovery tx for tokens' , async function ( ) {
787+ const balance = '100' ;
788+ const data = {
789+ hbars : '1' ,
790+ tokens : [ { tokenId : tokenId , balance : balance , decimals : 6 } ] ,
791+ } ;
792+ const getBalanceStub = sandBox . stub ( Hbar . prototype , 'getAccountBalance' ) . resolves ( data ) ;
793+
794+ const recovery = await basecoin . recover ( {
795+ userKey,
796+ backupKey,
797+ rootAddress,
798+ walletPassphrase,
799+ recoveryDestination : recoveryDestination + '?memoId=' + memo ,
800+ tokenId : tokenId ,
801+ } ) ;
802+
803+ recovery . should . not . be . undefined ( ) ;
804+ recovery . should . have . property ( 'id' ) ;
805+ recovery . should . have . property ( 'tx' ) ;
806+ recovery . should . have . property ( 'coin' , 'thbar' ) ;
807+ recovery . should . have . property ( 'nodeId' , defaultNodeId ) ;
808+ getBalanceStub . callCount . should . equal ( 1 ) ;
809+ const txBuilder = basecoin . getBuilderFactory ( ) . from ( recovery . tx ) ;
810+ const tx = await txBuilder . build ( ) ;
811+ tx . toBroadcastFormat ( ) . should . equal ( recovery . tx ) ;
812+ const txJson = tx . toJson ( ) ;
813+ txJson . amount . should . equal ( balance ) ;
814+ txJson . to . should . equal ( recoveryDestination ) ;
815+ txJson . from . should . equal ( rootAddress ) ;
816+ txJson . fee . should . equal ( defaultFee ) ;
817+ txJson . node . should . equal ( defaultNodeId ) ;
818+ txJson . memo . should . equal ( memo ) ;
819+ txJson . validDuration . should . equal ( defaultValidDuration ) ;
820+ txJson . should . have . property ( 'startTime' ) ;
821+ recovery . should . have . property ( 'startTime' , txJson . startTime ) ;
822+ recovery . should . have . property ( 'id' , rootAddress + '@' + txJson . startTime ) ;
823+ } ) ;
824+
825+ it ( 'should throw error for non supported invalid tokenId' , async function ( ) {
826+ const invalidTokenId = 'randomstring' ;
827+ const data = {
828+ hbars : '1' ,
829+ tokens : [ { tokenId : tokenId , balance : '100' , decimals : 6 } ] ,
830+ } ;
831+ sandBox . stub ( Hbar . prototype , 'getAccountBalance' ) . resolves ( data ) ;
832+ await assert . rejects (
833+ async ( ) => {
834+ await basecoin . recover ( {
835+ userKey,
836+ backupKey,
837+ rootAddress : rootAddress ,
838+ walletPassphrase,
839+ recoveryDestination : recoveryDestination + '?memoId=' + memo ,
840+ tokenId : invalidTokenId ,
841+ } ) ;
842+ } ,
843+ { message : 'Unsupported token: ' + invalidTokenId }
844+ ) ;
845+ } ) ;
846+
847+ it ( 'should throw error for insufficient balance for tokenId if token balance not exist' , async function ( ) {
848+ const data = {
849+ hbars : '100' ,
850+ tokens : [ { tokenId : 'randomString' , balance : '100' , decimals : 6 } ] ,
851+ } ;
852+ sandBox . stub ( Hbar . prototype , 'getAccountBalance' ) . resolves ( data ) ;
853+ await assert . rejects (
854+ async ( ) => {
855+ await basecoin . recover ( {
856+ userKey,
857+ backupKey,
858+ rootAddress : rootAddress ,
859+ walletPassphrase,
860+ recoveryDestination : recoveryDestination + '?memoId=' + memo ,
861+ tokenId : tokenId ,
862+ } ) ;
863+ } ,
864+ { message : 'Insufficient balance to recover token: ' + tokenId + ' for account: ' + rootAddress }
865+ ) ;
866+ } ) ;
867+
868+ it ( 'should throw error for insufficient balance for tokenId if token balance exist with 0 amount' , async function ( ) {
869+ const data = {
870+ hbars : '100' ,
871+ tokens : [ { tokenId : 'randomString' , balance : '0' , decimals : 6 } ] ,
872+ } ;
873+ sandBox . stub ( Hbar . prototype , 'getAccountBalance' ) . resolves ( data ) ;
874+ await assert . rejects (
875+ async ( ) => {
876+ await basecoin . recover ( {
877+ userKey,
878+ backupKey,
879+ rootAddress : rootAddress ,
880+ walletPassphrase,
881+ recoveryDestination : recoveryDestination + '?memoId=' + memo ,
882+ tokenId : tokenId ,
883+ } ) ;
884+ } ,
885+ { message : 'Insufficient balance to recover token: ' + tokenId + ' for account: ' + rootAddress }
886+ ) ;
887+ } ) ;
888+
889+ it ( 'should throw error for insufficient native balance for token transfer' , async function ( ) {
890+ const data = {
891+ hbars : '0.01' ,
892+ tokens : [ { tokenId : tokenId , balance : '10' , decimals : 6 } ] ,
893+ } ;
894+ sandBox . stub ( Hbar . prototype , 'getAccountBalance' ) . resolves ( data ) ;
895+ await assert . rejects (
896+ async ( ) => {
897+ await basecoin . recover ( {
898+ userKey,
899+ backupKey,
900+ rootAddress : rootAddress ,
901+ walletPassphrase,
902+ recoveryDestination : recoveryDestination + '?memoId=' + memo ,
903+ tokenId : tokenId ,
904+ } ) ;
905+ } ,
906+ { message : 'Insufficient native balance to recover tokens, got native balance: 1000000 fee: ' + defaultFee }
907+ ) ;
908+ } ) ;
784909 } ) ;
785910
786911 describe ( 'Unsigned Sweep' , function ( ) {
787912 const sandBox = Sinon . createSandbox ( ) ;
788913 let getBalanceStub : SinonStub ;
789914
790- beforeEach ( function ( ) {
791- getBalanceStub = sandBox
792- . stub ( Hbar . prototype , 'getAccountBalance' )
793- . resolves ( { hbars : formatBalanceResponse ( balance ) , tokens : [ ] } ) ;
794- } ) ;
795-
796915 afterEach ( function ( ) {
797916 sandBox . verifyAndRestore ( ) ;
798917 } ) ;
799918
800919 it ( 'should build unsigned sweep tx' , async function ( ) {
920+ getBalanceStub = sandBox
921+ . stub ( Hbar . prototype , 'getAccountBalance' )
922+ . resolves ( { hbars : formatBalanceResponse ( balance ) , tokens : [ ] } ) ;
801923 const startTime = ( Date . now ( ) / 1000 + 10 ) . toFixed ( ) ; // timestamp in seconds, 10 seconds from now
802924 const expectedAmount = new BigNumber ( balance ) . minus ( defaultFee ) . toString ( ) ;
803925
@@ -842,6 +964,58 @@ describe('Hedera Hashgraph:', function () {
842964 txJson . validDuration . should . equal ( defaultValidDuration ) ;
843965 } ) ;
844966
967+ it ( 'should build unsigned sweep tx for tokens' , async function ( ) {
968+ const balance = '100' ;
969+ const data = {
970+ hbars : '1' ,
971+ tokens : [ { tokenId : tokenId , balance : balance , decimals : 6 } ] ,
972+ } ;
973+ getBalanceStub = sandBox . stub ( Hbar . prototype , 'getAccountBalance' ) . resolves ( data ) ;
974+ const startTime = ( Date . now ( ) / 1000 + 10 ) . toFixed ( ) ; // timestamp in seconds, 10 seconds from now
975+ const recovery = await basecoin . recover ( {
976+ userKey : userPub ,
977+ backupKey : backupPub ,
978+ rootAddress,
979+ bitgoKey,
980+ recoveryDestination : recoveryDestination + '?memoId=' + memo ,
981+ startTime,
982+ tokenId : tokenId ,
983+ } ) ;
984+
985+ getBalanceStub . callCount . should . equal ( 1 ) ;
986+
987+ recovery . should . not . be . undefined ( ) ;
988+ recovery . should . have . property ( 'txHex' ) ;
989+ recovery . should . have . property ( 'id' , rootAddress + '@' + startTime + '.0' ) ;
990+ recovery . should . have . property ( 'userKey' , userPub ) ;
991+ recovery . should . have . property ( 'backupKey' , backupPub ) ;
992+ recovery . should . have . property ( 'bitgoKey' , bitgoKey ) ;
993+ recovery . should . have . property ( 'address' , rootAddress ) ;
994+ recovery . should . have . property ( 'coin' , 'thbar' ) ;
995+ recovery . should . have . property ( 'maxFee' , defaultFee . toString ( ) ) ;
996+ recovery . should . have . property ( 'recipients' , [
997+ { address : recoveryDestination , amount : balance , tokenName : 'thbar:usdc' } ,
998+ ] ) ;
999+ recovery . should . have . property ( 'amount' , balance ) ;
1000+ recovery . should . have . property ( 'validDuration' , defaultValidDuration ) ;
1001+ recovery . should . have . property ( 'nodeId' , defaultNodeId ) ;
1002+ recovery . should . have . property ( 'memo' , memo ) ;
1003+ recovery . should . have . property ( 'startTime' , startTime + '.0' ) ;
1004+ const txBuilder = basecoin . getBuilderFactory ( ) . from ( recovery . txHex ) ;
1005+ const tx = await txBuilder . build ( ) ;
1006+ const txJson = tx . toJson ( ) ;
1007+ txJson . id . should . equal ( rootAddress + '@' + startTime + '.0' ) ;
1008+ txJson . amount . should . equal ( balance ) ;
1009+ txJson . to . should . equal ( recoveryDestination ) ;
1010+ txJson . from . should . equal ( rootAddress ) ;
1011+ txJson . fee . should . equal ( defaultFee ) ;
1012+ txJson . node . should . equal ( defaultNodeId ) ;
1013+ txJson . memo . should . equal ( memo ) ;
1014+ txJson . validDuration . should . equal ( defaultValidDuration ) ;
1015+ txJson . startTime . should . equal ( startTime + '.0' ) ;
1016+ txJson . validDuration . should . equal ( defaultValidDuration ) ;
1017+ } ) ;
1018+
8451019 it ( 'should throw if startTime is undefined' , async function ( ) {
8461020 const startTime = undefined ;
8471021
0 commit comments