@@ -21,6 +21,26 @@ function getReqUrl(req: { url: string; hostname: string }): URL {
21
21
return new URL ( req . url , `http://${ req . hostname } ` ) ;
22
22
}
23
23
24
+ // https://github.com/stacks-network/stacks-core/blob/20d5137438c7d169ea97dd2b6a4d51b8374a4751/stackslib/src/chainstate/stacks/db/blocks.rs#L338
25
+ const MINIMUM_TX_FEE_RATE_PER_BYTE = 1 ;
26
+
27
+ interface FeeEstimation {
28
+ fee : number ;
29
+ fee_rate : number ;
30
+ }
31
+ interface FeeEstimateResponse {
32
+ cost_scalar_change_by_byte : number ;
33
+ estimated_cost : {
34
+ read_count : number ;
35
+ read_length : number ;
36
+ runtime : number ;
37
+ write_count : number ;
38
+ write_length : number ;
39
+ } ;
40
+ estimated_cost_scalar : number ;
41
+ estimations : [ FeeEstimation , FeeEstimation , FeeEstimation ] ;
42
+ }
43
+
24
44
export const CoreNodeRpcProxyRouter : FastifyPluginAsync <
25
45
Record < never , never > ,
26
46
Server ,
@@ -117,10 +137,22 @@ export const CoreNodeRpcProxyRouter: FastifyPluginAsync<
117
137
}
118
138
) ;
119
139
140
+ let feeEstimationModifier : number | null = null ;
141
+ fastify . addHook ( 'onReady' , ( ) => {
142
+ const feeEstEnvVar = process . env [ 'STACKS_CORE_FEE_ESTIMATION_MODIFIER' ] ;
143
+ if ( feeEstEnvVar ) {
144
+ const parsed = parseFloat ( feeEstEnvVar ) ;
145
+ if ( ! isNaN ( parsed ) && parsed > 0 ) {
146
+ feeEstimationModifier = parsed ;
147
+ }
148
+ }
149
+ } ) ;
150
+
120
151
await fastify . register ( fastifyHttpProxy , {
121
152
upstream : `http://${ stacksNodeRpcEndpoint } ` ,
122
153
rewritePrefix : '/v2' ,
123
154
http2 : false ,
155
+ globalAgent : true ,
124
156
preValidation : async ( req , reply ) => {
125
157
if ( getReqUrl ( req ) . pathname !== '/v2/transactions' ) {
126
158
return ;
@@ -201,6 +233,29 @@ export const CoreNodeRpcProxyRouter: FastifyPluginAsync<
201
233
const txId = responseBuffer . toString ( ) ;
202
234
await logTxBroadcast ( txId ) ;
203
235
await reply . send ( responseBuffer ) ;
236
+ } else if (
237
+ getReqUrl ( req ) . pathname === '/v2/fees/transaction' &&
238
+ reply . statusCode === 200 &&
239
+ feeEstimationModifier !== null
240
+ ) {
241
+ const reqBody = req . body as {
242
+ estimated_len ?: number ;
243
+ transaction_payload : string ;
244
+ } ;
245
+ // https://github.com/stacks-network/stacks-core/blob/20d5137438c7d169ea97dd2b6a4d51b8374a4751/stackslib/src/net/api/postfeerate.rs#L200-L201
246
+ const txSize = Math . max (
247
+ reqBody . estimated_len ?? 0 ,
248
+ reqBody . transaction_payload . length / 2
249
+ ) ;
250
+ const minFee = txSize * MINIMUM_TX_FEE_RATE_PER_BYTE ;
251
+ const modifier = feeEstimationModifier ;
252
+ const responseBuffer = await readRequestBody ( response as ServerResponse ) ;
253
+ const responseJson = JSON . parse ( responseBuffer . toString ( ) ) as FeeEstimateResponse ;
254
+ responseJson . estimations . forEach ( estimation => {
255
+ // max(min fee, estimate returned by node * configurable modifier)
256
+ estimation . fee = Math . max ( minFee , Math . round ( estimation . fee * modifier ) ) ;
257
+ } ) ;
258
+ await reply . removeHeader ( 'content-length' ) . send ( JSON . stringify ( responseJson ) ) ;
204
259
} else {
205
260
await reply . send ( response ) ;
206
261
}
0 commit comments