init339398989
This commit is contained in:
@@ -1,34 +0,0 @@
|
|||||||
# Local .env for docker compose ${REDIS_PASSWORD} interpolation.
|
|
||||||
# DO NOT COMMIT (already in .gitignore). На прод-боксе оператор создаёт свой через `cp .env.example .env`.
|
|
||||||
|
|
||||||
VAULT_ADDR=
|
|
||||||
VAULT_ROLE_ID=
|
|
||||||
VAULT_SECRET_ID=
|
|
||||||
VAULT_MOUNT_POINT=dev-secrets
|
|
||||||
VAULT_SECRET_PATH=database
|
|
||||||
VAULT_JWT_KID_PATH=jwt/kid
|
|
||||||
VAULT_JWT_KIDS_PREFIX=jwt/kids
|
|
||||||
VAULT_CSRF_PATH=csrf
|
|
||||||
VAULT_CRYPTO_KEY_PATH=crypto/master
|
|
||||||
|
|
||||||
JWT_ALGORITHM=RS256
|
|
||||||
JWT_ISSUER=bitok
|
|
||||||
JWT_AUDIENCE=elcsa
|
|
||||||
|
|
||||||
API_PORT=3001
|
|
||||||
LOG_LEVEL=INFO
|
|
||||||
|
|
||||||
CORS_ORIGINS=*
|
|
||||||
CORS_ALLOW_CREDENTIALS=false
|
|
||||||
|
|
||||||
REDIS_HOST=keydb
|
|
||||||
REDIS_PORT=6379
|
|
||||||
REDIS_PASSWORD=0O7klMYUvwwR19UORSzEtsRn9kUPnDyfkJ9GDH2yMERYV0vRCU
|
|
||||||
REDIS_DB=0
|
|
||||||
|
|
||||||
# Price oracle (CoinGecko free tier — без ключа работает).
|
|
||||||
COINGECKO_API_KEY=
|
|
||||||
|
|
||||||
# Outbound proxy для swap + bridge endpoints.
|
|
||||||
# Если задан — Jupiter/Relay/RPC calls идут через proxy. Read-only direct.
|
|
||||||
OUTBOUND_PROXY_URL=http://37.220.84.34:3128
|
|
||||||
74
.env.example
74
.env.example
@@ -1,74 +0,0 @@
|
|||||||
# ─────────────────────────────────────────────────────────────────────
|
|
||||||
# Production .env template для CryptoWallet API.
|
|
||||||
# Скопируй: cp .env.example .env && chmod 600 .env && nano .env
|
|
||||||
# ─────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
# ─── HashiCorp Vault (для master-key, JWT public keys, CSRF secret) ──
|
|
||||||
VAULT_ADDR=https://vault.your-domain.com
|
|
||||||
VAULT_ROLE_ID=00000000-0000-0000-0000-000000000000
|
|
||||||
VAULT_SECRET_ID=00000000-0000-0000-0000-000000000000
|
|
||||||
VAULT_MOUNT_POINT=dev-secrets
|
|
||||||
VAULT_SECRET_PATH=crypto/master
|
|
||||||
VAULT_JWT_KID_PATH=jwt/kid
|
|
||||||
VAULT_JWT_KIDS_PREFIX=jwt/kids/
|
|
||||||
VAULT_CSRF_PATH=csrf
|
|
||||||
VAULT_CRYPTO_KEY_PATH=crypto/master
|
|
||||||
|
|
||||||
# ─── JWT (приём от bitok external issuer) ────────────────────────────
|
|
||||||
JWT_ALGORITHM=RS256
|
|
||||||
JWT_ISSUER=bitok
|
|
||||||
JWT_AUDIENCE=cryptowallet-api
|
|
||||||
|
|
||||||
# ─── API runtime ─────────────────────────────────────────────────────
|
|
||||||
API_PORT=3001
|
|
||||||
LOG_LEVEL=info
|
|
||||||
|
|
||||||
# CORS — comma-separated list разрешённых origins (фронтенд hosts)
|
|
||||||
CORS_ORIGINS=https://app.your-domain.com,https://admin.your-domain.com
|
|
||||||
CORS_ALLOW_CREDENTIALS=true
|
|
||||||
|
|
||||||
# ─── Postgres (для docker-compose) ───────────────────────────────────
|
|
||||||
# Эти переменные используются compose'ом для создания/connect к Postgres контейнеру.
|
|
||||||
# Если оператор использует external managed Postgres — игнорь POSTGRES_* и впиши
|
|
||||||
# connection string в DATABASE_URL ниже.
|
|
||||||
POSTGRES_USER=cryptowallet
|
|
||||||
POSTGRES_PASSWORD=__GENERATE_STRONG_PASSWORD__
|
|
||||||
POSTGRES_DB=cryptowallet
|
|
||||||
|
|
||||||
# Если используешь managed/external Postgres — раскомментируй и заполни:
|
|
||||||
# DATABASE_URL=postgres://user:pass@host:5432/dbname
|
|
||||||
|
|
||||||
# ─── KeyDB / Redis (idempotency cache + NearIntents asset map cache) ─
|
|
||||||
# REDIS_HOST=keydb имя service в compose — НЕ меняй если работаешь через compose
|
|
||||||
REDIS_HOST=keydb
|
|
||||||
REDIS_PORT=6379
|
|
||||||
REDIS_PASSWORD=__GENERATE_STRONG_PASSWORD__
|
|
||||||
REDIS_DB=0
|
|
||||||
|
|
||||||
# ─── Внешние API ─────────────────────────────────────────────────────
|
|
||||||
# CoinGecko — для prices/dynamics (без ключа работает с rate limits)
|
|
||||||
COINGECKO_API_KEY=
|
|
||||||
|
|
||||||
# Jupiter — для SOL custodial swap (без ключа = lower rate limits)
|
|
||||||
JUPITER_API_KEY=
|
|
||||||
|
|
||||||
# Jupiter referral — если хочешь чтобы SOL swap fees шли через Jupiter feeAccount.
|
|
||||||
# Сейчас у нас атомарный 0.7% fee atomic в swap-orchestrator (на APP_FEE_WALLET_SOL),
|
|
||||||
# referral отдельный механизм. Можно оставить пустым.
|
|
||||||
JUPITER_REFERRAL_ACCOUNT=
|
|
||||||
|
|
||||||
# TronGrid — для TRX queries (без ключа 3 req/sec, с ключом 100/sec)
|
|
||||||
# Получить: https://www.trongrid.io/dashboard
|
|
||||||
TRON_API_KEY=
|
|
||||||
|
|
||||||
# Outbound HTTP proxy (опц.) — если хотите чтобы Jupiter/Relay/NearIntents calls
|
|
||||||
# шли через proxy (rotation IP для rate limits). Формат: http://user:pass@host:port
|
|
||||||
OUTBOUND_PROXY_URL=
|
|
||||||
|
|
||||||
# ─── App fee wallets ─────────────────────────────────────────────────
|
|
||||||
# Эти адреса HARDCODED в backend (apps/api/src/lib/app-fee.ts), НЕ настраиваются
|
|
||||||
# через env. Если нужно изменить — code review + rebuild.
|
|
||||||
# EVM (ETH+BSC): 0xeb9fbf0d137ef5ea7b9959044c2ed44ec1206c68
|
|
||||||
# SOL: DQkQegoX698XkcXZ6VX9P1qUpbV64Sgjz1BCPFgfWpjD
|
|
||||||
# TRX: TRwpFjnfMBe4aDJbHYEqeUVCG1auF8wFXP
|
|
||||||
# BTC: — (not collectable)
|
|
||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
signAndBroadcastEvmFeeTx,
|
signAndBroadcastEvmFeeTx,
|
||||||
signAndBroadcastSolanaTx,
|
signAndBroadcastSolanaTx,
|
||||||
} from './wallet-signer.service';
|
} from './wallet-signer.service';
|
||||||
import { computeAppFee, getAppFeeWallet, APP_FEE_WALLET_SOL, APP_FEE_WALLET_TRX } from '../lib/app-fee';
|
import { computeAppFee, APP_FEE_WALLET_SOL, APP_FEE_WALLET_TRX } from '../lib/app-fee';
|
||||||
import { SOL_TOKENS, TRX_TOKENS } from '../lib/token-registry';
|
import { SOL_TOKENS, TRX_TOKENS } from '../lib/token-registry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -448,8 +448,6 @@ async function executeSol(
|
|||||||
// ── App fee 0.7% (atomic — fee tx ПЕРЕД main bridge) ──
|
// ── App fee 0.7% (atomic — fee tx ПЕРЕД main bridge) ──
|
||||||
// Native SOL bridge → fee in native SOL. SPL bridge → fee in same SPL token.
|
// Native SOL bridge → fee in native SOL. SPL bridge → fee in same SPL token.
|
||||||
// Если fee = 0 (amount слишком мал) → throw (anti-bypass).
|
// Если fee = 0 (amount слишком мал) → throw (anti-bypass).
|
||||||
let feeTxid: string | undefined;
|
|
||||||
let feeAmount: string | undefined;
|
|
||||||
const feeAmountBig = computeAppFee(p.fromAmount);
|
const feeAmountBig = computeAppFee(p.fromAmount);
|
||||||
if (feeAmountBig <= 0n) {
|
if (feeAmountBig <= 0n) {
|
||||||
throw new Error('SOL bridge: fromAmount too small — fee = 0');
|
throw new Error('SOL bridge: fromAmount too small — fee = 0');
|
||||||
@@ -466,8 +464,8 @@ async function executeSol(
|
|||||||
amount: feeAmountBig.toString(),
|
amount: feeAmountBig.toString(),
|
||||||
token: feeSymbol,
|
token: feeSymbol,
|
||||||
});
|
});
|
||||||
feeTxid = feeRes.txid;
|
const feeTxid = feeRes.txid;
|
||||||
feeAmount = feeAmountBig.toString();
|
const feeAmount = feeAmountBig.toString();
|
||||||
logger.info(`SOL bridge fee broadcast: ${feeAmount} ${feeSymbol || 'lamports'} → ${APP_FEE_WALLET_SOL} (txid ${feeTxid})`);
|
logger.info(`SOL bridge fee broadcast: ${feeAmount} ${feeSymbol || 'lamports'} → ${APP_FEE_WALLET_SOL} (txid ${feeTxid})`);
|
||||||
|
|
||||||
// Для SOL LiFi возвращает в `transactionRequest.data` = base64-encoded VersionedTransaction.
|
// Для SOL LiFi возвращает в `transactionRequest.data` = base64-encoded VersionedTransaction.
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ export async function signAndBroadcastRawTron(p: SignRawTronParams): Promise<{ t
|
|||||||
// Если у нас неизвестный selector (LiFi bridge calls, custom routers) — мы НЕ можем
|
// Если у нас неизвестный selector (LiFi bridge calls, custom routers) — мы НЕ можем
|
||||||
// передать "0x..." как function_selector (TronGrid keccak'нёт строку и получит
|
// передать "0x..." как function_selector (TronGrid keccak'нёт строку и получит
|
||||||
// полностью другие 4 байта → contract revert). Используем `data` напрямую.
|
// полностью другие 4 байта → contract revert). Используем `data` напрямую.
|
||||||
let data = p.callData.startsWith('0x') ? p.callData.slice(2) : p.callData;
|
const data = p.callData.startsWith('0x') ? p.callData.slice(2) : p.callData;
|
||||||
if (data.length < 8) throw new Error('TRX call data too short (need >= 4-byte selector)');
|
if (data.length < 8) throw new Error('TRX call data too short (need >= 4-byte selector)');
|
||||||
const selector8 = data.slice(0, 8);
|
const selector8 = data.slice(0, 8);
|
||||||
const knownCanonical = lookupKnownSelector(selector8);
|
const knownCanonical = lookupKnownSelector(selector8);
|
||||||
@@ -294,7 +294,12 @@ export async function signAndBroadcastRawTron(p: SignRawTronParams): Promise<{ t
|
|||||||
if (!simOk) {
|
if (!simOk) {
|
||||||
const rawMsg = simRes?.result?.message;
|
const rawMsg = simRes?.result?.message;
|
||||||
const msgDecoded = rawMsg
|
const msgDecoded = rawMsg
|
||||||
? Buffer.from(rawMsg, 'hex').toString().replace(/[ | |||||||