1- import { binToHex , hexToBin } from '@bitauth/libauth' ;
1+ import { binToHex , decodeTransactionUnsafe , hexToBin , isHex } from '@bitauth/libauth' ;
22import { sha256 } from '@cashscript/utils' ;
33import { Utxo , Network } from '../interfaces.js' ;
44import NetworkProvider from './NetworkProvider.js' ;
5- import { addressToLockScript , randomUtxo } from '../utils.js' ;
5+ import { addressToLockScript , libauthTokenDetailsToCashScriptTokenDetails , randomUtxo } from '../utils.js' ;
66
77// redeclare the addresses from vars.ts instead of importing them
88const aliceAddress = 'bchtest:qpgjmwev3spwlwkgmyjrr2s2cvlkkzlewq62mzgjnp' ;
99const bobAddress = 'bchtest:qz6q5gqnxdldkr07xpls5474mmzmlesd6qnux4skuc' ;
1010const carolAddress = 'bchtest:qqsr7nqwe6rq5crj63gy5gdqchpnwmguusmr7tfmsj' ;
1111
12+ interface MockNetworkProviderOptions {
13+ updateUtxoSet : boolean ;
14+ }
15+
16+ // We are setting the default updateUtxoSet to 'false' so that it doesn't break the current behaviour
17+ // TODO: in a future breaking release we want to set this to 'true' by default
1218export default class MockNetworkProvider implements NetworkProvider {
13- private utxoMap : Record < string , Utxo [ ] > = { } ;
19+ // we use lockingBytecode hex as the key for utxoMap to make cash addresses and token addresses interchangeable
20+ private utxoSet : Array < [ string , Utxo ] > = [ ] ;
1421 private transactionMap : Record < string , string > = { } ;
1522 public network : Network = Network . MOCKNET ;
1623 public blockHeight : number = 133700 ;
24+ public options : MockNetworkProviderOptions ;
25+
26+ constructor ( options ?: Partial < MockNetworkProviderOptions > ) {
27+ this . options = { updateUtxoSet : false , ...options } ;
1728
18- constructor ( ) {
1929 for ( let i = 0 ; i < 3 ; i += 1 ) {
2030 this . addUtxo ( aliceAddress , randomUtxo ( ) ) ;
2131 this . addUtxo ( bobAddress , randomUtxo ( ) ) ;
@@ -24,8 +34,8 @@ export default class MockNetworkProvider implements NetworkProvider {
2434 }
2535
2636 async getUtxos ( address : string ) : Promise < Utxo [ ] > {
27- const lockingBytecode = binToHex ( addressToLockScript ( address ) ) ;
28- return this . utxoMap [ lockingBytecode ] ?? [ ] ;
37+ const addressLockingBytecode = binToHex ( addressToLockScript ( address ) ) ;
38+ return this . utxoSet . filter ( ( [ lockingBytecode ] ) => lockingBytecode === addressLockingBytecode ) . map ( ( [ , utxo ] ) => utxo ) ;
2939 }
3040
3141 setBlockHeight ( newBlockHeight : number ) : void {
@@ -44,21 +54,55 @@ export default class MockNetworkProvider implements NetworkProvider {
4454 const transactionBin = hexToBin ( txHex ) ;
4555
4656 const txid = binToHex ( sha256 ( sha256 ( transactionBin ) ) . reverse ( ) ) ;
57+
58+ if ( this . options . updateUtxoSet && this . transactionMap [ txid ] ) {
59+ throw new Error ( `Transaction with txid ${ txid } was already submitted` ) ;
60+ }
61+
4762 this . transactionMap [ txid ] = txHex ;
63+
64+ // If updateUtxoSet is false, we don't need to update the utxo set, and just return the txid
65+ if ( ! this . options . updateUtxoSet ) return txid ;
66+
67+ const decodedTransaction = decodeTransactionUnsafe ( transactionBin ) ;
68+
69+ decodedTransaction . inputs . forEach ( ( input ) => {
70+ const utxoIndex = this . utxoSet . findIndex (
71+ ( [ , utxo ] ) => utxo . txid === binToHex ( input . outpointTransactionHash ) && utxo . vout === input . outpointIndex ,
72+ ) ;
73+
74+ // TODO: we should check what error a BCHN node throws, so we can throw the same error here
75+ if ( utxoIndex === - 1 ) {
76+ throw new Error ( `UTXO not found for input ${ input . outpointIndex } of transaction ${ txid } ` ) ;
77+ }
78+
79+ this . utxoSet . splice ( utxoIndex , 1 ) ;
80+ } ) ;
81+
82+ decodedTransaction . outputs . forEach ( ( output , vout ) => {
83+ this . addUtxo ( binToHex ( output . lockingBytecode ) , {
84+ txid,
85+ vout,
86+ satoshis : output . valueSatoshis ,
87+ token : output . token && libauthTokenDetailsToCashScriptTokenDetails ( output . token ) ,
88+ } ) ;
89+ } ) ;
90+
4891 return txid ;
4992 }
5093
51- addUtxo ( address : string , utxo : Utxo ) : void {
52- const lockingBytecode = binToHex ( addressToLockScript ( address ) ) ;
53- if ( ! this . utxoMap [ lockingBytecode ] ) {
54- this . utxoMap [ lockingBytecode ] = [ ] ;
55- }
94+ // Note: the user can technically add the same UTXO multiple times (txid + vout), to the same or different addresses
95+ // but we don't check for this in the sendRawTransaction method. We might want to prevent duplicates from being added
96+ // in the first place.
97+ addUtxo ( addressOrLockingBytecode : string , utxo : Utxo ) : void {
98+ const lockingBytecode = isHex ( addressOrLockingBytecode ) ?
99+ addressOrLockingBytecode : binToHex ( addressToLockScript ( addressOrLockingBytecode ) ) ;
56100
57- this . utxoMap [ lockingBytecode ] . push ( utxo ) ;
101+ this . utxoSet . push ( [ lockingBytecode , utxo ] ) ;
58102 }
59103
60104 reset ( ) : void {
61- this . utxoMap = { } ;
105+ this . utxoSet = [ ] ;
62106 this . transactionMap = { } ;
63107 }
64108}
0 commit comments