11import { decodeTransactionUnsafe , hexToBin , stringify } from '@bitauth/libauth' ;
2- import { Contract , SignatureTemplate , ElectrumNetworkProvider , MockNetworkProvider } from '../src/index.js' ;
2+ import { Contract , SignatureTemplate , ElectrumNetworkProvider , MockNetworkProvider , placeholderP2PKHUnlocker , placeholderPublicKey , placeholderSignature } from '../src/index.js' ;
33import {
44 bobAddress ,
55 bobPub ,
@@ -16,6 +16,7 @@ import p2pkhArtifact from './fixture/p2pkh.artifact.js';
1616import twtArtifact from './fixture/transfer_with_timeout.artifact.js' ;
1717import { TransactionBuilder } from '../src/TransactionBuilder.js' ;
1818import { gatherUtxos , getTxOutputs } from './test-util.js' ;
19+ import { generateWcTransactionObjectFixture } from './fixture/walletconnect/fixtures.js' ;
1920
2021describe ( 'Transaction Builder' , ( ) => {
2122 const provider = process . env . TESTS_USE_MOCKNET
@@ -136,131 +137,160 @@ describe('Transaction Builder', () => {
136137 } ) ;
137138 } ) ;
138139
139- it ( 'should build a transaction that can spend from 2 different contracts and P2PKH + OP_RETURN' , async ( ) => {
140- const fee = 1000n ;
140+ describe ( 'test TransactionBuilder.build' , ( ) => {
141+ it ( 'should build a transaction that can spend from 2 different contracts and P2PKH + OP_RETURN' , async ( ) => {
142+ const fee = 1000n ;
141143
142- const carolUtxos = ( await provider . getUtxos ( carolAddress ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
143- const p2pkhUtxos = ( await p2pkhInstance . getUtxos ( ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
144- const twtUtxos = ( await twtInstance . getUtxos ( ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
144+ const carolUtxos = ( await provider . getUtxos ( carolAddress ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
145+ const p2pkhUtxos = ( await p2pkhInstance . getUtxos ( ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
146+ const twtUtxos = ( await twtInstance . getUtxos ( ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
145147
146- const change = carolUtxos [ 0 ] . satoshis - fee ;
147- const dustAmount = calculateDust ( { to : carolAddress , amount : change } ) ;
148+ const change = carolUtxos [ 0 ] . satoshis - fee ;
149+ const dustAmount = calculateDust ( { to : carolAddress , amount : change } ) ;
148150
149- const outputs = [
150- { to : p2pkhInstance . address , amount : p2pkhUtxos [ 0 ] . satoshis } ,
151- { to : twtInstance . address , amount : twtUtxos [ 0 ] . satoshis } ,
152- ...( change > dustAmount ? [ { to : carolAddress , amount : change } ] : [ ] ) ,
153- ] ;
151+ const outputs = [
152+ { to : p2pkhInstance . address , amount : p2pkhUtxos [ 0 ] . satoshis } ,
153+ { to : twtInstance . address , amount : twtUtxos [ 0 ] . satoshis } ,
154+ ...( change > dustAmount ? [ { to : carolAddress , amount : change } ] : [ ] ) ,
155+ ] ;
154156
155- if ( change < 0 ) {
156- throw new Error ( 'Not enough funds to send transaction' ) ;
157- }
157+ if ( change < 0 ) {
158+ throw new Error ( 'Not enough funds to send transaction' ) ;
159+ }
158160
159- const tx = new TransactionBuilder ( { provider } )
160- . addInput ( p2pkhUtxos [ 0 ] , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
161- . addInput ( twtUtxos [ 0 ] , twtInstance . unlock . transfer ( new SignatureTemplate ( carolPriv ) ) )
162- . addInput ( carolUtxos [ 0 ] , new SignatureTemplate ( carolPriv ) . unlockP2PKH ( ) )
163- . addOpReturnOutput ( [ 'Hello new transaction builder' ] )
164- . addOutputs ( outputs )
165- . build ( ) ;
161+ const tx = new TransactionBuilder ( { provider } )
162+ . addInput ( p2pkhUtxos [ 0 ] , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
163+ . addInput ( twtUtxos [ 0 ] , twtInstance . unlock . transfer ( new SignatureTemplate ( carolPriv ) ) )
164+ . addInput ( carolUtxos [ 0 ] , new SignatureTemplate ( carolPriv ) . unlockP2PKH ( ) )
165+ . addOpReturnOutput ( [ 'Hello new transaction builder' ] )
166+ . addOutputs ( outputs )
167+ . build ( ) ;
166168
167- const txOutputs = getTxOutputs ( decodeTransactionUnsafe ( hexToBin ( tx ) ) ) ;
168- expect ( txOutputs ) . toEqual ( expect . arrayContaining ( outputs ) ) ;
169- } ) ;
169+ const txOutputs = getTxOutputs ( decodeTransactionUnsafe ( hexToBin ( tx ) ) ) ;
170+ expect ( txOutputs ) . toEqual ( expect . arrayContaining ( outputs ) ) ;
171+ } ) ;
172+
173+ it ( 'should fail when fee is higher than maxFee' , async ( ) => {
174+ const fee = 2000n ;
175+ const maxFee = 1000n ;
176+ const p2pkhUtxos = ( await p2pkhInstance . getUtxos ( ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
170177
171- it ( 'should fail when fee is higher than maxFee' , async ( ) => {
172- const fee = 2000n ;
173- const maxFee = 1000n ;
174- const p2pkhUtxos = ( await p2pkhInstance . getUtxos ( ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
178+ const amount = p2pkhUtxos [ 0 ] . satoshis - fee ;
179+ const dustAmount = calculateDust ( { to : p2pkhInstance . address , amount } ) ;
175180
176- const amount = p2pkhUtxos [ 0 ] . satoshis - fee ;
177- const dustAmount = calculateDust ( { to : p2pkhInstance . address , amount } ) ;
181+ if ( amount < dustAmount ) {
182+ throw new Error ( 'Not enough funds to send transaction' ) ;
183+ }
184+
185+ expect ( ( ) => {
186+ new TransactionBuilder ( { provider } )
187+ . addInput ( p2pkhUtxos [ 0 ] , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
188+ . addOutput ( { to : p2pkhInstance . address , amount } )
189+ . setMaxFee ( maxFee )
190+ . build ( ) ;
191+ } ) . toThrow ( `Transaction fee of ${ fee } is higher than max fee of ${ maxFee } ` ) ;
192+ } ) ;
193+
194+ it ( 'should succeed when fee is lower than maxFee' , async ( ) => {
195+ const fee = 1000n ;
196+ const maxFee = 2000n ;
197+ const p2pkhUtxos = ( await p2pkhInstance . getUtxos ( ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
178198
179- if ( amount < dustAmount ) {
180- throw new Error ( 'Not enough funds to send transaction' ) ;
181- }
199+ const amount = p2pkhUtxos [ 0 ] . satoshis - fee ;
200+ const dustAmount = calculateDust ( { to : p2pkhInstance . address , amount } ) ;
182201
183- expect ( ( ) => {
184- new TransactionBuilder ( { provider } )
202+ if ( amount < dustAmount ) {
203+ throw new Error ( 'Not enough funds to send transaction' ) ;
204+ }
205+
206+ const tx = new TransactionBuilder ( { provider } )
185207 . addInput ( p2pkhUtxos [ 0 ] , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
186208 . addOutput ( { to : p2pkhInstance . address , amount } )
187209 . setMaxFee ( maxFee )
188210 . build ( ) ;
189- } ) . toThrow ( `Transaction fee of ${ fee } is higher than max fee of ${ maxFee } ` ) ;
190- } ) ;
191211
192- it ( 'should succeed when fee is lower than maxFee' , async ( ) => {
193- const fee = 1000n ;
194- const maxFee = 2000n ;
195- const p2pkhUtxos = ( await p2pkhInstance . getUtxos ( ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
196-
197- const amount = p2pkhUtxos [ 0 ] . satoshis - fee ;
198- const dustAmount = calculateDust ( { to : p2pkhInstance . address , amount } ) ;
212+ expect ( tx ) . toBeDefined ( ) ;
213+ } ) ;
199214
200- if ( amount < dustAmount ) {
201- throw new Error ( 'Not enough funds to send transaction' ) ;
202- }
215+ // TODO: Consider improving error messages checked below to also include the input/output index
203216
204- const tx = new TransactionBuilder ( { provider } )
205- . addInput ( p2pkhUtxos [ 0 ] , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
206- . addOutput ( { to : p2pkhInstance . address , amount } )
207- . setMaxFee ( maxFee )
208- . build ( ) ;
217+ it ( 'should fail when trying to send to invalid address' , async ( ) => {
218+ const p2pkhUtxos = ( await p2pkhInstance . getUtxos ( ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
209219
210- expect ( tx ) . toBeDefined ( ) ;
211- } ) ;
220+ expect ( ( ) => {
221+ new TransactionBuilder ( { provider } )
222+ . addInput ( p2pkhUtxos [ 0 ] , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
223+ . addOutput ( { to : bobAddress . slice ( 0 , - 1 ) , amount : 1000n } )
224+ . build ( ) ;
225+ } ) . toThrow ( 'CashAddress decoding error' ) ;
226+ } ) ;
212227
213- // TODO: Consider improving error messages checked below to also include the input/output index
228+ it ( 'should fail when trying to send tokens to non-token address' , async ( ) => {
229+ const tokenUtxo = ( await p2pkhInstance . getUtxos ( ) ) . find ( isFungibleTokenUtxo ) ! ;
214230
215- it ( 'should fail when trying to send to invalid address' , async ( ) => {
216- const p2pkhUtxos = ( await p2pkhInstance . getUtxos ( ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
231+ expect ( ( ) => {
232+ new TransactionBuilder ( { provider } )
233+ . addInput ( tokenUtxo , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
234+ . addOutput ( { to : bobAddress , amount : 1000n , token : tokenUtxo . token } )
235+ . build ( ) ;
236+ } ) . toThrow ( 'Tried to send tokens to an address without token support' ) ;
237+ } ) ;
217238
218- expect ( ( ) => {
219- new TransactionBuilder ( { provider } )
220- . addInput ( p2pkhUtxos [ 0 ] , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
221- . addOutput ( { to : bobAddress . slice ( 0 , - 1 ) , amount : 1000n } )
222- . build ( ) ;
223- } ) . toThrow ( 'CashAddress decoding error' ) ;
224- } ) ;
239+ it ( 'should fail when trying to send negative BCH amount or token amount' , async ( ) => {
240+ const tokenUtxo = ( await p2pkhInstance . getUtxos ( ) ) . find ( isFungibleTokenUtxo ) ! ;
241+
242+ expect ( ( ) => {
243+ new TransactionBuilder ( { provider } )
244+ . addInput ( tokenUtxo , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
245+ . addOutput ( { to : bobTokenAddress , amount : - 1000n , token : tokenUtxo . token } )
246+ . build ( ) ;
247+ } ) . toThrow ( 'Tried to add an output with -1000 satoshis, which is less than the required minimum for this output-type' ) ;
248+
249+ expect ( ( ) => {
250+ new TransactionBuilder ( { provider } )
251+ . addInput ( tokenUtxo , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
252+ . addOutput ( { to : bobTokenAddress , amount : 1000n , token : { amount : - 1000n , category : tokenUtxo . token ! . category } } )
253+ . build ( ) ;
254+ } ) . toThrow ( 'Tried to add an output with -1000 tokens, which is invalid' ) ;
255+ } ) ;
225256
226- it ( 'should fail when trying to send tokens to non-token address' , async ( ) => {
227- const tokenUtxo = ( await p2pkhInstance . getUtxos ( ) ) . find ( isFungibleTokenUtxo ) ! ;
257+ it ( 'should fail when adding undefined input' , async ( ) => {
258+ const p2pkhUtxos = ( await p2pkhInstance . getUtxos ( ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
259+ const undefinedUtxo = p2pkhUtxos [ 1000 ] ;
228260
229- expect ( ( ) => {
230- new TransactionBuilder ( { provider } )
231- . addInput ( tokenUtxo , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
232- . addOutput ( { to : bobAddress , amount : 1000n , token : tokenUtxo . token } )
233- . build ( ) ;
234- } ) . toThrow ( 'Tried to send tokens to an address without token support' ) ;
261+ expect ( ( ) => {
262+ new TransactionBuilder ( { provider } )
263+ . addInput ( undefinedUtxo , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
264+ . addOutput ( { to : bobAddress , amount : 1000n } )
265+ . build ( ) ;
266+ } ) . toThrow ( 'Input is undefined' ) ;
267+ } ) ;
235268 } ) ;
236269
237- it ( 'should fail when trying to send negative BCH amount or token amount' , async ( ) => {
238- const tokenUtxo = ( await p2pkhInstance . getUtxos ( ) ) . find ( isFungibleTokenUtxo ) ! ;
270+ describe ( 'test TransactionBuilder.generateWcTransactionObject' , ( ) => {
271+ it ( 'should match the generateWcTransactionObjectFixture ' , async ( ) => {
272+ const p2pkhUtxos = ( await p2pkhInstance . getUtxos ( ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
273+ const contractUtxo = p2pkhUtxos [ 0 ] ;
274+ const bobUtxos = await provider . getUtxos ( bobAddress ) ;
239275
240- expect ( ( ) => {
241- new TransactionBuilder ( { provider } )
242- . addInput ( tokenUtxo , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
243- . addOutput ( { to : bobTokenAddress , amount : - 1000n , token : tokenUtxo . token } )
244- . build ( ) ;
245- } ) . toThrow ( 'Tried to add an output with -1000 satoshis, which is less than the required minimum for this output-type' ) ;
276+ const placeholderUnlocker = placeholderP2PKHUnlocker ( bobAddress ) ;
277+ const placeholderPubKey = placeholderPublicKey ( ) ;
278+ const placeholderSig = placeholderSignature ( ) ;
246279
247- expect ( ( ) => {
248- new TransactionBuilder ( { provider } )
249- . addInput ( tokenUtxo , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
250- . addOutput ( { to : bobTokenAddress , amount : 1000n , token : { amount : - 1000n , category : tokenUtxo . token ! . category } } )
251- . build ( ) ;
252- } ) . toThrow ( 'Tried to add an output with -1000 tokens, which is invalid' ) ;
253- } ) ;
280+ // use the CashScript SDK to construct a transaction
281+ const transactionBuilder = new TransactionBuilder ( { provider } )
282+ . addInput ( contractUtxo , p2pkhInstance . unlock . spend ( placeholderPubKey , placeholderSig ) )
283+ . addInput ( bobUtxos [ 0 ] , placeholderUnlocker )
284+ . addOutput ( { to : bobAddress , amount : 100_000n } ) ;
254285
255- it ( 'should fail when adding undefined input' , async ( ) => {
256- const p2pkhUtxos = ( await p2pkhInstance . getUtxos ( ) ) . filter ( isNonTokenUtxo ) . sort ( utxoComparator ) . reverse ( ) ;
257- const undefinedUtxo = p2pkhUtxos [ 1000 ] ;
286+ // Generate WalletConnect transaction object with custom 'broadcast' and 'userPrompt' options
287+ const wcTransactionObj = transactionBuilder . generateWcTransactionObject ( {
288+ broadcast : true ,
289+ userPrompt : 'Example Contract transaction' ,
290+ } ) ;
258291
259- expect ( ( ) => {
260- new TransactionBuilder ( { provider } )
261- . addInput ( undefinedUtxo , p2pkhInstance . unlock . spend ( carolPub , new SignatureTemplate ( carolPriv ) ) )
262- . addOutput ( { to : bobAddress , amount : 1000n } )
263- . build ( ) ;
264- } ) . toThrow ( 'Input is undefined' ) ;
292+ const expectedResult = generateWcTransactionObjectFixture ;
293+ expect ( JSON . parse ( stringify ( wcTransactionObj ) ) ) . toEqual ( expectedResult ) ;
294+ } ) ;
265295 } ) ;
266296} ) ;
0 commit comments