This commit is contained in:
ZOMBIIIIIII
2026-05-13 00:37:00 +03:00
parent 7d9907be9c
commit 3a890b79ee
5 changed files with 230 additions and 43 deletions

View File

@@ -30,9 +30,8 @@ export const WalletController = {
* GET /api/wallets — все адреса юзера.
*/
async getWallets(req: Request, res: Response) {
const userId = '01KPKAFN6J1NJBY15DX8JE2QYB';
try {
const wallets = await WalletModel.findByUserId(userId);
const wallets = await WalletModel.findByUserId(req.auth!.userId);
res.json({
success: true,
data: wallets.map((w) => ({
@@ -42,7 +41,7 @@ export const WalletController = {
})),
});
} catch (err: any) {
logger.error(`getWallets failed for user ${userId}: ${err.stack || err.message}`);
logger.error(`getWallets failed for user ${req.auth!.userId}: ${err.stack || err.message}`);
res.status(500).json({ success: false, error: 'Internal error' });
}
},
@@ -54,7 +53,7 @@ export const WalletController = {
* Возвращает: ТОЛЬКО адреса. Mnemonic клиенту не отдаём.
*/
async createWallet(req: Request, res: Response) {
const userId = '01KPKAFN6J1NJBY15DX8JE2QYB';
const userId = req.auth!.userId;
if (!isCryptoReady()) {
res.status(503).json({ success: false, error: 'Crypto service not ready' });
@@ -65,20 +64,36 @@ export const WalletController = {
try {
await UserModel.ensureExists(userId);
if (await UserModel.hasMnemonic(userId)) {
// H14 — gate wallet creation behind KYC verification (опционально, controlled by env).
// Если REQUIRE_KYC_FOR_WALLET=true в env, требуется kyc_verified=true.
if (process.env.REQUIRE_KYC_FOR_WALLET === 'true') {
const isVerified = await UserModel.isKycVerified(userId);
if (!isVerified) {
res.status(403).json({ success: false, error: 'KYC verification required before wallet creation' });
return;
}
}
// H27 — claim placeholder ПЕРЕД derive. Loser race не тратит CPU + memory secrets.
// Атомарно: UPDATE WHERE encrypted_mnemonic IS NULL SET = 'PENDING_DERIVATION'
const claimResult = await UserModel.claimWalletSlot(userId);
if (claimResult === 'already_has') {
res.status(409).json({ success: false, error: 'Wallet already exists' });
return;
}
if (claimResult === 'no_user') {
res.status(404).json({ success: false, error: 'User not found or deleted' });
return;
}
// claimResult === 'claimed' — proceed
mnemonic = generateMnemonic();
const derived = await deriveAllAddresses(mnemonic);
const blob = encryptMnemonic(mnemonic);
const created = await db.transaction(async (trx) => {
const claimed = await UserModel.setEncryptedMnemonicIfAbsent(userId, blob, trx);
if (!claimed) {
throw new ConflictError();
}
// H32 — finalize placeholder (must succeed since claim won earlier)
await UserModel.finalizeWalletSlot(userId, blob, trx);
await WalletModel.createMany(
derived.map((w) => ({
user_id: userId,
@@ -88,8 +103,7 @@ export const WalletController = {
})),
trx,
);
// Дублируем ETH-адрес в users.erc20 — это поле прода-схемы
// (custodial wallet's ETH address, доступный через простой SELECT без JOIN'а на wallets).
// Дублируем ETH-адрес в users.erc20
const ethWallet = derived.find((w) => w.chain === 'ETH');
if (ethWallet) {
await UserModel.setErc20Address(userId, ethWallet.address, trx);