feat: /faucet/{address} в шлюз — налив SOL+фантиков на произвольный адрес (localnet)

This commit is contained in:
Caffeine
2026-09-10 05:23:50 +03:00
parent f426f58778
commit bf9d05f2f2
+64 -12
View File
@@ -15,6 +15,9 @@
* Ответ: {tx, id, bettor, entryPrice}
* POST /close {id} — close_bet по текущей цене (подпись relayer)
* GET /balance/{address} — {sol, solLamports, tokenUnits, tokenHuman}
* GET /faucet/{address} — налив SOL + фантиков на произвольный адрес
* (только localnet, allowAirdrop=1; иначе 403). Ответ:
* {address, sol, solLamports, tokenUnits, tokenHuman}
*
* Запуск: npm run gateway (ts-node src/gateway.ts)
* Env: GW_PORT (default 8895), GW_BETTOR_KEYSTORE, SMART_* (как у релейера).
@@ -299,11 +302,17 @@ async function handleClose(ctx: Ctx, body: Record<string, unknown>): Promise<Htt
return { status: 200, body: bodyOut };
}
async function handleBalance(ctx: Ctx, address: string): Promise<HttpJson> {
const pub = parsePubkey(address);
if (!pub) {
return { status: 400, body: { error: "address is not a valid Solana pubkey" } };
}
/** Балансы адреса (SOL + фантики в ATA по нашему mint) — переиспользуется
* и /balance, и /faucet (реальные значения после налива). */
async function readBalances(
ctx: Ctx,
pub: PublicKey
): Promise<{
sol: number;
solLamports: number;
tokenUnits: string;
tokenHuman: string;
}> {
const [lamports, tokenUnits] = await Promise.all([
ctx.connection.getBalance(pub),
(async (): Promise<string> => {
@@ -316,14 +325,52 @@ async function handleBalance(ctx: Ctx, address: string): Promise<HttpJson> {
}
})(),
]);
return {
sol: lamports / LAMPORTS_PER_SOL_NUM,
solLamports: lamports,
tokenUnits,
tokenHuman: humanize(tokenUnits, ctx.decimals),
};
}
async function handleBalance(ctx: Ctx, address: string): Promise<HttpJson> {
const pub = parsePubkey(address);
if (!pub) {
return { status: 400, body: { error: "address is not a valid Solana pubkey" } };
}
return { status: 200, body: await readBalances(ctx, pub) };
}
/**
* Налив фантиков/SOL на ПРОИЗВОЛЬНЫЙ адрес (Phantom и т.п.) — только localnet.
* Переиспользует готовые функции ops.ts без дублирования логики:
* - SOL (рента/комиссия) — тот же airdrop, что fundBettor;
* - фантики — mintTo в ATA адреса (до startBalance, дефолт = maxAmount),
* та же функция, которой /bet фандит ATA (ensureBettorTokenAccount).
* Ответ — адрес + реальные балансы после налива (readBalances, как в /balance).
*/
async function handleFaucet(ctx: Ctx, address: string): Promise<HttpJson> {
if (!ctx.cfg.allowAirdrop) {
return { status: 403, body: { error: "faucet only on localnet" } };
}
const pub = parsePubkey(address);
if (!pub) {
return { status: 400, body: { error: "address is not a valid Solana pubkey" } };
}
// SOL под ренту/комиссию — тот же airdrop, что fundBettor.
await fundBettor(ctx.connection, pub);
// Фантики — mintTo в ATA адреса до startBalance (дефолт = maxAmount из Global).
await ensureBettorTokenAccount(
ctx.connection,
ctx.admin,
ctx.cfg.tokenMint,
pub,
BigInt(ctx.cfg.startBalance)
);
const balances = await readBalances(ctx, pub);
return {
status: 200,
body: {
sol: lamports / LAMPORTS_PER_SOL_NUM,
solLamports: lamports,
tokenUnits,
tokenHuman: humanize(tokenUnits, ctx.decimals),
},
body: { address: pub.toBase58(), ...balances },
};
}
@@ -435,6 +482,9 @@ async function main(): Promise<void> {
} else if (m === "GET" && p.startsWith("/balance/")) {
const address = decodeURIComponent(p.slice("/balance/".length));
result = await handleBalance(ctx, address);
} else if (m === "GET" && p.startsWith("/faucet/")) {
const address = decodeURIComponent(p.slice("/faucet/".length));
result = await handleFaucet(ctx, address);
} else {
result = { status: 404, body: { error: "not found" } };
}
@@ -461,7 +511,9 @@ async function main(): Promise<void> {
server.listen(port, "0.0.0.0", () => {
console.log(`gateway: listening on 0.0.0.0:${port} (RPC ${cfg.rpcUrl}, mint ${cfg.tokenMint.toBase58()})`);
console.log(`gateway: endpoints: GET /state, POST /bet, POST /close, GET /balance/{address}`);
console.log(
`gateway: endpoints: GET /state, POST /bet, POST /close, GET /balance/{address}, GET /faucet/{address}`
);
});
}