@@ -4,10 +4,14 @@ import { addAsync, RouterWithAsync } from '@awaitjs/express';
4
4
import * as btc from 'bitcoinjs-lib' ;
5
5
import PQueue from 'p-queue' ;
6
6
import * as BN from 'bn.js' ;
7
- import { makeSTXTokenTransfer , StacksNetwork } from '@blockstack/stacks-transactions' ;
7
+ import {
8
+ makeSTXTokenTransfer ,
9
+ SignedTokenTransferOptions ,
10
+ StacksNetwork ,
11
+ } from '@blockstack/stacks-transactions' ;
8
12
import { makeBtcFaucetPayment , getBtcBalance } from '../../btc-faucet' ;
9
13
import { DataStore , DbFaucetRequestCurrency } from '../../datastore/common' ;
10
- import { logger , stxToMicroStx } from '../../helpers' ;
14
+ import { assertNotNullish as unwrap , logger , stxToMicroStx } from '../../helpers' ;
11
15
import { testnetKeys , GetStacksTestnetNetwork } from './debug' ;
12
16
import { StacksCoreRpcClient } from '../../core-rpc/client' ;
13
17
@@ -88,6 +92,8 @@ export function createFaucetRouter(db: DataStore): RouterWithAsync {
88
92
const FAUCET_STACKING_WINDOW = 2 * 24 * 60 * 60 * 1000 ; // 2 days
89
93
const FAUCET_STACKING_TRIGGER_COUNT = 1 ;
90
94
95
+ const MAX_NONCE_INCREMENT_RETRIES = 5 ;
96
+
91
97
router . postAsync ( '/stx' , async ( req , res ) => {
92
98
await stxFaucetRequestQueue . add ( async ( ) => {
93
99
const address : string = req . query . address || req . body . address ;
@@ -120,30 +126,51 @@ export function createFaucetRouter(db: DataStore): RouterWithAsync {
120
126
return ;
121
127
}
122
128
123
- const tx = await makeSTXTokenTransfer ( {
124
- recipient : address ,
125
- amount : new BN ( stxAmount . toString ( ) ) ,
126
- senderKey : privateKey ,
127
- network : getStxFaucetNetwork ( ) ,
128
- memo : 'Faucet' ,
129
- } ) ;
130
-
129
+ let nextNonce : BN | undefined = undefined ;
130
+ let sendError : Error | undefined = undefined ;
131
+ let sendSuccess = false ;
132
+ for ( let i = 0 ; i < MAX_NONCE_INCREMENT_RETRIES ; i ++ ) {
133
+ const txOpts : SignedTokenTransferOptions = {
134
+ recipient : address ,
135
+ amount : new BN ( stxAmount . toString ( ) ) ,
136
+ senderKey : privateKey ,
137
+ network : getStxFaucetNetwork ( ) ,
138
+ memo : 'Faucet' ,
139
+ } ;
140
+ if ( nextNonce !== undefined ) {
141
+ txOpts . nonce = nextNonce ;
142
+ }
143
+ const tx = await makeSTXTokenTransfer ( txOpts ) ;
144
+ const serializedTx = tx . serialize ( ) ;
145
+ try {
146
+ const txSendResult = await new StacksCoreRpcClient ( ) . sendTransaction ( serializedTx ) ;
147
+ res . json ( {
148
+ success : true ,
149
+ txId : txSendResult . txId ,
150
+ txRaw : tx . serialize ( ) . toString ( 'hex' ) ,
151
+ } ) ;
152
+ sendSuccess = true ;
153
+ break ;
154
+ } catch ( error ) {
155
+ sendError = error ;
156
+ if ( error . message ?. includes ( 'ConflictingNonceInMempool' ) ) {
157
+ nextNonce = unwrap ( tx . auth . spendingCondition ) . nonce . add ( new BN ( 1 ) ) ;
158
+ } else if ( error . message ?. includes ( 'BadNonce' ) ) {
159
+ nextNonce = undefined ;
160
+ } else {
161
+ throw error ;
162
+ }
163
+ }
164
+ }
165
+ if ( ! sendSuccess && sendError ) {
166
+ throw sendError ;
167
+ }
131
168
await db . insertFaucetRequest ( {
132
169
ip : `${ ip } ` ,
133
170
address : address ,
134
171
currency : DbFaucetRequestCurrency . STX ,
135
172
occurred_at : now ,
136
173
} ) ;
137
-
138
- const hex = tx . serialize ( ) . toString ( 'hex' ) ;
139
- const serializedTx = tx . serialize ( ) ;
140
- const { txId } = await new StacksCoreRpcClient ( ) . sendTransaction ( serializedTx ) ;
141
-
142
- res . json ( {
143
- success : true ,
144
- txId,
145
- txRaw : hex ,
146
- } ) ;
147
174
} ) ;
148
175
} ) ;
149
176
0 commit comments