Skip to content

Commit be5db0c

Browse files
committed
feat: support multiple STX faucet source accounts (#1946)
* feat: support multiple STX faucet source accounts * fix: always start attempt on index 0 * fix: guard against array overflow
1 parent a8cd825 commit be5db0c

File tree

2 files changed

+35
-18
lines changed

2 files changed

+35
-18
lines changed

.env

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ STACKS_CORE_RPC_PORT=20443
9393
# STACKS_FAUCET_NODE_HOST=<IP or hostname>
9494
# STACKS_FAUCET_NODE_PORT=<port number>
9595

96+
# A comma-separated list of STX private keys which will send faucet transactions to accounts that
97+
# request them. Attempts will always be made from the first account, only once transaction chaining
98+
# gets too long the faucet will start using the next one.
99+
# FAUCET_PRIVATE_KEY=
100+
96101
## configure the chainID/networkID; testnet: 0x80000000, mainnet: 0x00000001
97102
STACKS_CHAIN_ID=0x00000001
98103

src/api/routes/faucets.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export function getStxFaucetNetworks(): StacksNetwork[] {
4242
enum TxSendResultStatus {
4343
Success,
4444
ConflictingNonce,
45+
TooMuchChaining,
4546
Error,
4647
}
4748

@@ -50,17 +51,12 @@ interface TxSendResultSuccess {
5051
txId: string;
5152
}
5253

53-
interface TxSendResultConflictingNonce {
54-
status: TxSendResultStatus.ConflictingNonce;
55-
error: Error;
56-
}
57-
5854
interface TxSendResultError {
59-
status: TxSendResultStatus.Error;
55+
status: TxSendResultStatus;
6056
error: Error;
6157
}
6258

63-
type TxSendResult = TxSendResultSuccess | TxSendResultConflictingNonce | TxSendResultError;
59+
type TxSendResult = TxSendResultSuccess | TxSendResultError;
6460

6561
function clientFromNetwork(network: StacksNetwork): StacksCoreRpcClient {
6662
const coreUrl = new URL(network.coreApiUrl);
@@ -148,6 +144,9 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
148144
const FAUCET_STACKING_WINDOW = 2 * 24 * 60 * 60 * 1000; // 2 days
149145
const FAUCET_STACKING_TRIGGER_COUNT = 1;
150146

147+
const STX_FAUCET_NETWORKS = getStxFaucetNetworks();
148+
const STX_FAUCET_KEYS = (process.env.FAUCET_PRIVATE_KEY ?? testnetKeys[0].secretKey).split(',');
149+
151150
router.post(
152151
'/stx',
153152
asyncHandler(async (req, res) => {
@@ -167,8 +166,6 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
167166
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
168167
const lastRequests = await db.getSTXFaucetRequests(address);
169168

170-
const privateKey = process.env.FAUCET_PRIVATE_KEY || testnetKeys[0].secretKey;
171-
172169
const isStackingReq = req.query['stacking'] === 'true';
173170

174171
// Guard condition: requests are limited to x times per y minutes.
@@ -191,10 +188,8 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
191188
return;
192189
}
193190

194-
const networks = getStxFaucetNetworks();
195-
196191
const stxAmounts: bigint[] = [];
197-
for (const network of networks) {
192+
for (const network of STX_FAUCET_NETWORKS) {
198193
try {
199194
let stxAmount = FAUCET_DEFAULT_STX_AMOUNT;
200195
if (isStackingReq) {
@@ -216,13 +211,14 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
216211

217212
const generateTx = async (
218213
network: StacksNetwork,
214+
keyIndex: number,
219215
nonce?: bigint,
220216
fee?: bigint
221217
): Promise<StacksTransaction> => {
222218
const txOpts: SignedTokenTransferOptions = {
223219
recipient: address,
224220
amount: stxAmount,
225-
senderKey: privateKey,
221+
senderKey: STX_FAUCET_KEYS[keyIndex],
226222
network: network,
227223
memo: 'Faucet',
228224
anchorMode: AnchorMode.Any,
@@ -242,7 +238,7 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
242238
/estimating transaction fee|NoEstimateAvailable/.test(error.message)
243239
) {
244240
const defaultFee = 200n;
245-
return await generateTx(network, nonce, defaultFee);
241+
return await generateTx(network, keyIndex, nonce, defaultFee);
246242
}
247243
throw error;
248244
}
@@ -251,9 +247,9 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
251247
const nonces: bigint[] = [];
252248
const fees: bigint[] = [];
253249
let txGenFetchError: Error | undefined;
254-
for (const network of networks) {
250+
for (const network of STX_FAUCET_NETWORKS) {
255251
try {
256-
const tx = await generateTx(network);
252+
const tx = await generateTx(network, 0);
257253
nonces.push(tx.auth.spendingCondition?.nonce ?? BigInt(0));
258254
fees.push(tx.auth.spendingCondition.fee);
259255
} catch (error: any) {
@@ -270,10 +266,11 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
270266
let retrySend = false;
271267
let sendSuccess: { txId: string; txRaw: string } | undefined;
272268
let lastSendError: Error | undefined;
269+
let stxKeyIndex = 0;
273270
do {
274-
const tx = await generateTx(networks[0], nextNonce, fee);
271+
const tx = await generateTx(STX_FAUCET_NETWORKS[0], stxKeyIndex, nextNonce, fee);
275272
const rawTx = Buffer.from(tx.serialize());
276-
for (const network of networks) {
273+
for (const network of STX_FAUCET_NETWORKS) {
277274
const rpcClient = clientFromNetwork(network);
278275
try {
279276
const res = await rpcClient.sendTransaction(rawTx);
@@ -289,6 +286,11 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
289286
status: TxSendResultStatus.ConflictingNonce,
290287
error,
291288
});
289+
} else if (error.message?.includes('TooMuchChaining')) {
290+
sendTxResults.push({
291+
status: TxSendResultStatus.TooMuchChaining,
292+
error,
293+
});
292294
} else {
293295
sendTxResults.push({
294296
status: TxSendResultStatus.Error,
@@ -305,6 +307,16 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
305307
retrySend = true;
306308
sendTxResults.length = 0;
307309
nextNonce = nextNonce + 1n;
310+
} else if (
311+
sendTxResults.every(res => res.status === TxSendResultStatus.TooMuchChaining)
312+
) {
313+
// Try with the next key in case we have one.
314+
if (stxKeyIndex + 1 === STX_FAUCET_KEYS.length) {
315+
retrySend = false;
316+
} else {
317+
retrySend = true;
318+
stxKeyIndex++;
319+
}
308320
} else {
309321
retrySend = false;
310322
}

0 commit comments

Comments
 (0)