@@ -12,8 +12,273 @@ import {
1212 PostCoinSignTx ,
1313} from '../../../src/typedRoutes/api/v2/coinSignTx' ;
1414import { assertDecode } from './common' ;
15+ import 'should' ;
16+ import 'should-http' ;
17+ import 'should-sinon' ;
18+ import * as sinon from 'sinon' ;
19+ import { BitGo } from 'bitgo' ;
20+ import { setupAgent } from '../../lib/testutil' ;
1521
1622describe ( 'CoinSignTx codec tests' , function ( ) {
23+ describe ( 'coinSignTx' , function ( ) {
24+ const agent = setupAgent ( ) ;
25+ const coin = 'tbtc' ;
26+
27+ const mockFullySignedResponse = {
28+ txHex :
29+ '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000' ,
30+ } ;
31+
32+ afterEach ( function ( ) {
33+ sinon . restore ( ) ;
34+ } ) ;
35+
36+ it ( 'should successfully sign a transaction' , async function ( ) {
37+ const requestBody = {
38+ txPrebuild : {
39+ txHex :
40+ '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000' ,
41+ walletId : '5a1341e7c8421dc90710673b3166bbd5' ,
42+ } ,
43+ prv : 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2' ,
44+ isLastSignature : true ,
45+ } ;
46+
47+ // Create mock coin with signTransaction method
48+ const mockCoin = {
49+ signTransaction : sinon . stub ( ) . resolves ( mockFullySignedResponse ) ,
50+ } ;
51+
52+ // Stub BitGo.prototype.coin to return our mock coin
53+ const coinStub = sinon . stub ( BitGo . prototype , 'coin' ) . returns ( mockCoin as any ) ;
54+
55+ // Make the request to Express
56+ const result = await agent
57+ . post ( `/api/v2/${ coin } /signtx` )
58+ . set ( 'Authorization' , 'Bearer test_access_token_12345' )
59+ . set ( 'Content-Type' , 'application/json' )
60+ . send ( requestBody ) ;
61+
62+ // Verify the response
63+ assert . strictEqual ( result . status , 200 ) ;
64+ result . body . should . have . property ( 'txHex' ) ;
65+ assert . strictEqual ( result . body . txHex , mockFullySignedResponse . txHex ) ;
66+
67+ // This ensures the response structure matches the typed definition
68+ const decodedResponse = assertDecode ( FullySignedTransactionResponse , result . body ) ;
69+ assert . strictEqual ( decodedResponse . txHex , mockFullySignedResponse . txHex ) ;
70+
71+ // Verify that the correct BitGoJS methods were called
72+ assert . strictEqual ( coinStub . calledOnceWith ( coin ) , true ) ;
73+ assert . strictEqual (
74+ mockCoin . signTransaction . calledOnceWith (
75+ sinon . match ( {
76+ coin : coin ,
77+ txPrebuild : sinon . match . object ,
78+ prv : requestBody . prv ,
79+ isLastSignature : true ,
80+ } )
81+ ) ,
82+ true
83+ ) ;
84+ } ) ;
85+
86+ it ( 'should successfully sign a half-signed transaction' , async function ( ) {
87+ const requestBody = {
88+ txPrebuild : {
89+ txBase64 :
90+ 'AQAAAAFz2JT3Xvjk8jKcYcMrKR8tPMRm5+/Q6J2sMgtz7QDpAAAAAAD+////AoCWmAAAAAAAGXapFJA29QPQaHHwR3Uriuhw2A6tHkPgiKwAAAAAAAEBH9cQ2QAAAAAAAXapFCf/zr8zPrMftHGIRsOt0Cf+wdOyiKwA' ,
91+ } ,
92+ prv : 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2' ,
93+ } ;
94+
95+ const mockHalfSignedResponse = {
96+ halfSigned : {
97+ txBase64 :
98+ 'AQAAAAFz2JT3Xvjk8jKcYcMrKR8tPMRm5+/Q6J2sMgtz7QDpAAAAAAD+////AoCWmAAAAAAAGXapFJA29QPQaHHwR3Uriuhw2A6tHkPgiKwAAAAAAAEBH9cQ2QAAAAAAAXapFCf/zr8zPrMftHGIRsOt0Cf+wdOyiKwA' ,
99+ } ,
100+ } ;
101+
102+ // Create mock coin with signTransaction method
103+ const mockCoin = {
104+ signTransaction : sinon . stub ( ) . resolves ( mockHalfSignedResponse ) ,
105+ } ;
106+
107+ // Stub BitGo.prototype.coin to return our mock coin
108+ const coinStub = sinon . stub ( BitGo . prototype , 'coin' ) . returns ( mockCoin as any ) ;
109+
110+ // Make the request to Express
111+ const result = await agent
112+ . post ( `/api/v2/${ coin } /signtx` )
113+ . set ( 'Authorization' , 'Bearer test_access_token_12345' )
114+ . set ( 'Content-Type' , 'application/json' )
115+ . send ( requestBody ) ;
116+
117+ // Verify the response
118+ assert . strictEqual ( result . status , 200 ) ;
119+ result . body . should . have . property ( 'halfSigned' ) ;
120+ result . body . halfSigned . should . have . property ( 'txBase64' ) ;
121+ assert . strictEqual ( result . body . halfSigned . txBase64 , mockHalfSignedResponse . halfSigned . txBase64 ) ;
122+
123+ // This ensures the response structure matches the typed definition
124+ const decodedResponse = assertDecode ( HalfSignedAccountTransactionResponse , result . body ) ;
125+ assert . strictEqual ( decodedResponse . halfSigned . txBase64 , mockHalfSignedResponse . halfSigned . txBase64 ) ;
126+
127+ // Verify that the correct BitGoJS methods were called
128+ assert . strictEqual ( coinStub . calledOnceWith ( coin ) , true ) ;
129+ assert . strictEqual ( mockCoin . signTransaction . calledOnce , true ) ;
130+ } ) ;
131+
132+ it ( 'should successfully return a transaction request ID' , async function ( ) {
133+ const requestBody = {
134+ txPrebuild : {
135+ txHex :
136+ '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000' ,
137+ } ,
138+ prv : 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2' ,
139+ } ;
140+
141+ const mockTxRequestResponse = {
142+ txRequestId : '5a1341e7c8421dc90710673b3166bbd5' ,
143+ } ;
144+
145+ // Create mock coin with signTransaction method
146+ const mockCoin = {
147+ signTransaction : sinon . stub ( ) . resolves ( mockTxRequestResponse ) ,
148+ } ;
149+
150+ // Stub BitGo.prototype.coin to return our mock coin
151+ const coinStub = sinon . stub ( BitGo . prototype , 'coin' ) . returns ( mockCoin as any ) ;
152+
153+ // Make the request to Express
154+ const result = await agent
155+ . post ( `/api/v2/${ coin } /signtx` )
156+ . set ( 'Authorization' , 'Bearer test_access_token_12345' )
157+ . set ( 'Content-Type' , 'application/json' )
158+ . send ( requestBody ) ;
159+
160+ // Verify the response
161+ assert . strictEqual ( result . status , 200 ) ;
162+ result . body . should . have . property ( 'txRequestId' ) ;
163+ assert . strictEqual ( result . body . txRequestId , mockTxRequestResponse . txRequestId ) ;
164+
165+ // This ensures the response structure matches the typed definition
166+ const decodedResponse = assertDecode ( SignedTransactionRequestResponse , result . body ) ;
167+ assert . strictEqual ( decodedResponse . txRequestId , mockTxRequestResponse . txRequestId ) ;
168+
169+ // Verify that the correct BitGoJS methods were called
170+ assert . strictEqual ( coinStub . calledOnceWith ( coin ) , true ) ;
171+ assert . strictEqual ( mockCoin . signTransaction . calledOnce , true ) ;
172+ } ) ;
173+
174+ it ( 'should successfully return a TSS transaction request (Full TxRequestResponse)' , async function ( ) {
175+ const requestBody = {
176+ txPrebuild : {
177+ txHex :
178+ '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000' ,
179+ } ,
180+ prv : 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2' ,
181+ } ;
182+
183+ const mockTxRequestFullResponse = {
184+ txRequestId : '5a1341e7c8421dc90710673b3166bbd5' ,
185+ walletId : '5a1341e7c8421dc90710673b3166bbd5' ,
186+ walletType : 'hot' ,
187+ version : 1 ,
188+ state : 'pendingApproval' ,
189+ date : '2023-01-01T00:00:00.000Z' ,
190+ createdDate : '2023-01-01T00:00:00.000Z' ,
191+ userId : '5a1341e7c8421dc90710673b3166bbd5' ,
192+ initiatedBy : '5a1341e7c8421dc90710673b3166bbd5' ,
193+ updatedBy : '5a1341e7c8421dc90710673b3166bbd5' ,
194+ intents : [ ] ,
195+ enterpriseId : '5a1341e7c8421dc90710673b3166bbd5' ,
196+ intent : { } ,
197+ pendingApprovalId : '5a1341e7c8421dc90710673b3166bbd5' ,
198+ policiesChecked : true ,
199+ signatureShares : [
200+ {
201+ from : 'user' ,
202+ to : 'bitgo' ,
203+ share : 'abc123' ,
204+ } ,
205+ ] ,
206+ commitmentShares : [
207+ {
208+ from : 'user' ,
209+ to : 'bitgo' ,
210+ share : 'abc123' ,
211+ type : 'commitment' ,
212+ } ,
213+ ] ,
214+ txHashes : [ 'hash1' , 'hash2' ] ,
215+ unsignedTxs : [
216+ {
217+ serializedTxHex :
218+ '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000' ,
219+ signableHex :
220+ '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000' ,
221+ derivationPath : "m/44'/0'/0'/0/0" ,
222+ } ,
223+ ] ,
224+ apiVersion : 'lite' ,
225+ latest : true ,
226+ } ;
227+
228+ // Create mock coin with signTransaction method
229+ const mockCoin = {
230+ signTransaction : sinon . stub ( ) . resolves ( mockTxRequestFullResponse ) ,
231+ } ;
232+
233+ // Stub BitGo.prototype.coin to return our mock coin
234+ const coinStub = sinon . stub ( BitGo . prototype , 'coin' ) . returns ( mockCoin as any ) ;
235+
236+ // Make the request to Express
237+ const result = await agent
238+ . post ( `/api/v2/${ coin } /signtx` )
239+ . set ( 'Authorization' , 'Bearer test_access_token_12345' )
240+ . set ( 'Content-Type' , 'application/json' )
241+ . send ( requestBody ) ;
242+
243+ // Verify the response
244+ assert . strictEqual ( result . status , 200 ) ;
245+ result . body . should . have . property ( 'txRequestId' ) ;
246+ result . body . should . have . property ( 'walletId' ) ;
247+ result . body . should . have . property ( 'version' ) ;
248+ result . body . should . have . property ( 'state' ) ;
249+ result . body . should . have . property ( 'intents' ) ;
250+ result . body . should . have . property ( 'latest' ) ;
251+ assert . strictEqual ( result . body . txRequestId , mockTxRequestFullResponse . txRequestId ) ;
252+ assert . strictEqual ( result . body . walletId , mockTxRequestFullResponse . walletId ) ;
253+ assert . strictEqual ( result . body . version , mockTxRequestFullResponse . version ) ;
254+ assert . strictEqual ( result . body . state , mockTxRequestFullResponse . state ) ;
255+ assert . strictEqual ( result . body . latest , mockTxRequestFullResponse . latest ) ;
256+
257+ // Verify TSS-specific fields
258+ result . body . should . have . property ( 'signatureShares' ) ;
259+ result . body . should . have . property ( 'commitmentShares' ) ;
260+ result . body . should . have . property ( 'unsignedTxs' ) ;
261+ result . body . signatureShares . should . be . Array ( ) ;
262+ result . body . signatureShares . should . have . length ( 1 ) ;
263+ result . body . commitmentShares . should . be . Array ( ) ;
264+ result . body . commitmentShares . should . have . length ( 1 ) ;
265+ result . body . unsignedTxs . should . be . Array ( ) ;
266+ result . body . unsignedTxs . should . have . length ( 1 ) ;
267+
268+ // This ensures the TSS transaction request response structure matches the typed definition
269+ const decodedResponse = assertDecode ( TxRequestResponse , result . body ) ;
270+ assert . strictEqual ( decodedResponse . txRequestId , mockTxRequestFullResponse . txRequestId ) ;
271+ assert . strictEqual ( decodedResponse . walletId , mockTxRequestFullResponse . walletId ) ;
272+ assert . strictEqual ( decodedResponse . version , mockTxRequestFullResponse . version ) ;
273+ assert . strictEqual ( decodedResponse . state , mockTxRequestFullResponse . state ) ;
274+ assert . strictEqual ( decodedResponse . latest , mockTxRequestFullResponse . latest ) ;
275+
276+ // Verify that the correct BitGoJS methods were called
277+ assert . strictEqual ( coinStub . calledOnceWith ( coin ) , true ) ;
278+ assert . strictEqual ( mockCoin . signTransaction . calledOnce , true ) ;
279+ } ) ;
280+ } ) ;
281+
17282 describe ( 'CoinSignTxParams' , function ( ) {
18283 it ( 'should validate params with required coin' , function ( ) {
19284 const validParams = {
0 commit comments