Files
cryptowallet/apps/api/src/lib/wallet-binding.ts
2026-05-13 00:17:32 +03:00

44 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* JWT ↔ wallet-address binding.
*
* Защита от drain-вектора: проксируемые endpoint'ы (swap-build, relay-quote) принимают
* `userAddress`/`recipient` как body param. Если не привязать к JWT-юзеру —
* authenticated user A может set `userAddress=<user B's addr>` и:
* - swap output идёт к B (бесплатный slippage steal)
* - reentrancy: B's address — malicious contract, callback дрянит
* - bridge `recipient=attacker` → victim signs → funds to attacker
*
* Этот хелпер находит wallet user'а по chain и проверяет match.
*/
import { WalletModel } from '../models/wallet.model';
import type { ChainCode } from './address-validators';
/**
* Throws if address ≠ user's wallet для данного chain.
* Returns the canonical (DB-stored) address для использования вместо user input.
*
* Сравнение case-insensitive только для EVM (где checksum mixed-case = same address).
* Для BTC/TRX/SOL — strict equality (base58/bech32 case-sensitive).
*/
export async function assertUserOwnsAddress(
userId: string,
chain: ChainCode,
candidateAddress: string,
): Promise<string> {
const wallet = await WalletModel.findByUserAndChain(userId, chain);
if (!wallet) {
throw new Error(`No ${chain} wallet for user — POST /wallets/create first`);
}
const isEvm = chain === 'ETH' || chain === 'BSC';
const dbAddr = wallet.address;
const candidate = String(candidateAddress ?? '').trim();
const match = isEvm
? candidate.toLowerCase() === dbAddr.toLowerCase()
: candidate === dbAddr;
if (!match) {
throw new Error(`Address ${candidate} does not match user's ${chain} wallet ${dbAddr}`);
}
return dbAddr;
}