feat: /faucet/{address} в шлюз — налив SOL+фантиков на произвольный адрес (localnet)
This commit is contained in:
+64
-12
@@ -15,6 +15,9 @@
|
|||||||
* Ответ: {tx, id, bettor, entryPrice}
|
* Ответ: {tx, id, bettor, entryPrice}
|
||||||
* POST /close {id} — close_bet по текущей цене (подпись relayer)
|
* POST /close {id} — close_bet по текущей цене (подпись relayer)
|
||||||
* GET /balance/{address} — {sol, solLamports, tokenUnits, tokenHuman}
|
* 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)
|
* Запуск: npm run gateway (ts-node src/gateway.ts)
|
||||||
* Env: GW_PORT (default 8895), GW_BETTOR_KEYSTORE, SMART_* (как у релейера).
|
* 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 };
|
return { status: 200, body: bodyOut };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleBalance(ctx: Ctx, address: string): Promise<HttpJson> {
|
/** Балансы адреса (SOL + фантики в ATA по нашему mint) — переиспользуется
|
||||||
const pub = parsePubkey(address);
|
* и /balance, и /faucet (реальные значения после налива). */
|
||||||
if (!pub) {
|
async function readBalances(
|
||||||
return { status: 400, body: { error: "address is not a valid Solana pubkey" } };
|
ctx: Ctx,
|
||||||
}
|
pub: PublicKey
|
||||||
|
): Promise<{
|
||||||
|
sol: number;
|
||||||
|
solLamports: number;
|
||||||
|
tokenUnits: string;
|
||||||
|
tokenHuman: string;
|
||||||
|
}> {
|
||||||
const [lamports, tokenUnits] = await Promise.all([
|
const [lamports, tokenUnits] = await Promise.all([
|
||||||
ctx.connection.getBalance(pub),
|
ctx.connection.getBalance(pub),
|
||||||
(async (): Promise<string> => {
|
(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 {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: { address: pub.toBase58(), ...balances },
|
||||||
sol: lamports / LAMPORTS_PER_SOL_NUM,
|
|
||||||
solLamports: lamports,
|
|
||||||
tokenUnits,
|
|
||||||
tokenHuman: humanize(tokenUnits, ctx.decimals),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,6 +482,9 @@ async function main(): Promise<void> {
|
|||||||
} else if (m === "GET" && p.startsWith("/balance/")) {
|
} else if (m === "GET" && p.startsWith("/balance/")) {
|
||||||
const address = decodeURIComponent(p.slice("/balance/".length));
|
const address = decodeURIComponent(p.slice("/balance/".length));
|
||||||
result = await handleBalance(ctx, address);
|
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 {
|
} else {
|
||||||
result = { status: 404, body: { error: "not found" } };
|
result = { status: 404, body: { error: "not found" } };
|
||||||
}
|
}
|
||||||
@@ -461,7 +511,9 @@ async function main(): Promise<void> {
|
|||||||
|
|
||||||
server.listen(port, "0.0.0.0", () => {
|
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: 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}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user