@@ -11,25 +11,37 @@ export default async function (req: Request, res: Response) {
1111 // Check funding availability - important to note that the assumption made here
1212 // is that there's only one utxo which corresponds to this address.
1313 const utxos = await woc . getUtxos ( address )
14- const max = utxos . reduce ( ( a , b ) => a + b . satoshis - 1 , 0 )
14+ const max = utxos . reduce ( ( a , b ) => a + b . satoshis , 0 )
15+
16+ const TOKEN_SATOSHIS = 13
17+ const MAX_TOKEN_OUTPUTS_PER_TX = 200
18+ const FUNDS_TX_FEE_BUFFER = 1000
19+ const TOKEN_TX_FEE_BUFFER = 1000
1520
1621 const lockingScript = new P2PKH ( ) . lock ( address ) . toHex ( )
1722
18- if ( max < 1000 ) {
23+ if ( max < TOKEN_SATOSHIS + FUNDS_TX_FEE_BUFFER ) {
1924 res . send ( { error : 'not enough satoshis, use the fund/{number} endpoint' , utxos } )
2025 return
2126 }
2227
2328 // create a bunch of funding transactions which we'll use to create tokens
24- let batches = Math . floor ( max / 1000 )
25- let change = max % 1000
29+ const maxAvailableForFundingOutputs = max - FUNDS_TX_FEE_BUFFER
30+ let batches = Math . ceil ( maxAvailableForFundingOutputs / ( TOKEN_SATOSHIS * MAX_TOKEN_OUTPUTS_PER_TX + TOKEN_TX_FEE_BUFFER ) )
31+ if ( batches < 1 ) {
32+ batches = 1
33+ }
2634
2735 // temp limit during dev
2836 if ( batches > 2 ) {
2937 batches = 2
3038 }
3139 // end of temp limit
3240
41+ const batchBase = Math . floor ( maxAvailableForFundingOutputs / batches )
42+ const batchRemainder = maxAvailableForFundingOutputs % batches
43+ const batchInputSatoshis = Array . from ( { length : batches } , ( _ , i ) => batchBase + ( i < batchRemainder ? 1 : 0 ) )
44+
3345 const fundsTx = new Transaction ( )
3446 utxos . forEach ( ( utxo ) => {
3547 fundsTx . addInput ( fromUtxo ( {
@@ -41,18 +53,15 @@ export default async function (req: Request, res: Response) {
4153 } )
4254
4355 for ( let i = 0 ; i < batches ; i ++ ) {
44- // for each batch we create an output of 1000 satoshis
45- fundsTx . addOutput ( {
46- satoshis : 1000 ,
47- lockingScript : new P2PKH ( ) . lock ( address )
48- } )
49- }
50- if ( change > 0 ) {
5156 fundsTx . addOutput ( {
52- change : true ,
57+ satoshis : batchInputSatoshis [ i ] ,
5358 lockingScript : new P2PKH ( ) . lock ( address )
5459 } )
5560 }
61+ fundsTx . addOutput ( {
62+ change : true ,
63+ lockingScript : new P2PKH ( ) . lock ( address )
64+ } )
5665
5766 await fundsTx . fee ( new SatoshisPerKilobyte ( 100 ) )
5867 await fundsTx . sign ( )
@@ -70,32 +79,63 @@ export default async function (req: Request, res: Response) {
7079
7180
7281 // Generate unique secret-hash pairs for each token
73- const secretPairs = [ ]
74- for ( let i = 0 ; i < batches * 957 ; i ++ ) {
75- const pair = HashPuzzle . generateSecretPair ( )
76- secretPairs . push ( pair )
77- }
82+ const secretsByTx : any [ ] = [ ]
83+ const tokenOutputsByTx : number [ ] = [ ]
7884
7985 const tokenCreationTxs : Transaction [ ] = [ ]
8086
8187 for ( let batch = 0 ; batch < batches ; batch ++ ) {
8288 // Create transaction with hash-locked outputs
83- const tx = new Transaction ( )
84- tx . addInput ( fromUtxo ( {
85- txid : fundsTxId ,
86- vout : batch ,
87- satoshis : 1000 ,
88- script : lockingScript ,
89- } , new P2PKH ( ) . unlock ( key ) ) )
90- secretPairs . slice ( batch * 957 , ( batch + 1 ) * 957 ) . forEach ( ( pair ) => {
89+ const batchSatoshis = batchInputSatoshis [ batch ]
90+ let outputs = Math . floor ( ( batchSatoshis - TOKEN_TX_FEE_BUFFER ) / TOKEN_SATOSHIS )
91+ if ( outputs < 1 ) {
92+ res . send ( { error : 'not enough satoshis to fund token creation transaction' , batch, batchSatoshis } )
93+ return
94+ }
95+ if ( outputs > MAX_TOKEN_OUTPUTS_PER_TX ) {
96+ outputs = MAX_TOKEN_OUTPUTS_PER_TX
97+ }
98+
99+ while ( outputs > 0 ) {
100+ const tx = new Transaction ( )
101+ tx . addInput ( fromUtxo ( {
102+ txid : fundsTxId ,
103+ vout : batch ,
104+ satoshis : batchSatoshis ,
105+ script : lockingScript ,
106+ } , new P2PKH ( ) . unlock ( key ) ) )
107+
108+ const pairs = [ ]
109+ for ( let i = 0 ; i < outputs ; i ++ ) {
110+ const pair = HashPuzzle . generateSecretPair ( )
111+ pairs . push ( pair )
112+ tx . addOutput ( {
113+ satoshis : TOKEN_SATOSHIS ,
114+ lockingScript : new HashPuzzle ( ) . lock ( pair . hash )
115+ } )
116+ }
117+
91118 tx . addOutput ( {
92- satoshis : 1 ,
93- lockingScript : new HashPuzzle ( ) . lock ( pair . hash )
119+ change : true ,
120+ lockingScript : new P2PKH ( ) . lock ( address )
94121 } )
95- } )
96- await tx . fee ( new SatoshisPerKilobyte ( 100 ) )
97- await tx . sign ( )
98- tokenCreationTxs . push ( tx )
122+
123+ try {
124+ await tx . fee ( new SatoshisPerKilobyte ( 100 ) )
125+ await tx . sign ( )
126+ tokenCreationTxs . push ( tx )
127+ secretsByTx . push ( pairs )
128+ tokenOutputsByTx . push ( outputs )
129+ break
130+ } catch ( e ) {
131+ outputs -= 1
132+ }
133+ }
134+
135+ if ( outputs === 0 ) {
136+ res . send ( { error : 'not enough satoshis to fund token creation transaction' , batch, batchSatoshis } )
137+ return
138+ }
99139 }
100140
101141 // Broadcast transactions
@@ -114,31 +154,33 @@ export default async function (req: Request, res: Response) {
114154 // Store transaction data
115155 const txDbResponse = await db . collection ( 'txs' ) . insertMany ( tokenTxs )
116156
117- const tokenUtxos = secretPairs . map ( ( secret , idx ) => {
118- const vout = idx % 957
119- const creationTx = Math . floor ( idx / 957 )
157+ const tokenUtxos : any [ ] = [ ]
158+ for ( let creationTx = 0 ; creationTx < tokenTxs . length ; creationTx ++ ) {
120159 const txid = tokenTxs [ creationTx ] . txid
121- const script = tokenCreationTxs [ creationTx ] . outputs [ vout ] . lockingScript . toHex ( )
122- const satoshis = 1
123- const fileHash = null
124- const confirmed = false
125- const spent = false
126- return {
127- txid,
128- vout,
129- script,
130- satoshis,
131- secret,
132- fileHash,
133- confirmed,
134- spent,
160+ for ( let vout = 0 ; vout < tokenOutputsByTx [ creationTx ] ; vout ++ ) {
161+ const secret = secretsByTx [ creationTx ] [ vout ]
162+ const script = tokenCreationTxs [ creationTx ] . outputs [ vout ] . lockingScript . toHex ( )
163+ const satoshis = TOKEN_SATOSHIS
164+ const fileHash = null
165+ const confirmed = false
166+ const spent = false
167+ tokenUtxos . push ( {
168+ txid,
169+ vout,
170+ script,
171+ satoshis,
172+ secret,
173+ fileHash,
174+ confirmed,
175+ spent,
176+ } )
135177 }
136- } )
178+ }
137179
138180 // Store token data
139181 const utxosDbResponse = await db . collection ( 'utxos' ) . insertMany ( tokenUtxos , { bypassDocumentValidation : true , ordered : false , forceServerObjectId : true } )
140182
141- res . send ( { txid : tokenUtxos [ 0 ] . txid , number : batches * 957 , txDb : txDbResponse . insertedCount , utxosDb : utxosDbResponse . insertedCount } )
183+ res . send ( { txid : tokenUtxos [ 0 ] . txid , number : tokenUtxos . length , txDb : txDbResponse . insertedCount , utxosDb : utxosDbResponse . insertedCount } )
142184 } catch ( error ) {
143185 console . log ( error )
144186 res . status ( 500 )
0 commit comments