swagger2
This commit is contained in:
@@ -6,10 +6,11 @@ import { getBalance, getTransactions } from '../services/wallet-ops.service';
|
||||
import { isValidAddress, isValidAmount, ChainCode } from '../lib/address-validators';
|
||||
import { generateMnemonic, deriveAllAddresses, ALL_CHAINS } from '../services/wallet-generator.service';
|
||||
import { encryptMnemonic, decryptMnemonic, isCryptoReady } from '../services/crypto.service';
|
||||
import { signAndBroadcast, signAndBroadcastRawEvm, signAndBroadcastSolanaTx } from '../services/wallet-signer.service';
|
||||
import { signAndBroadcast, signAndBroadcastRawEvm, signAndBroadcastSolanaTx, signAndBroadcastSolanaInstructions } from '../services/wallet-signer.service';
|
||||
import { swapBsc, swapTrx, swapSol } from '../services/swap-orchestrator.service';
|
||||
import { getEvmFeeTiers, type FeeTier } from '../services/gas-oracle.service';
|
||||
import { applyEvmTxPolicy } from '../lib/evm-tx-policy';
|
||||
import { getRelayTrustedAddresses } from '../lib/relay-trusted-cache';
|
||||
import { acquireSendLock } from '../lib/send-lock';
|
||||
import { claimIdempotency, saveIdempotencyResponse, extractIdempotencyKey } from '../lib/idempotency';
|
||||
import { auditLog, auditLogStrict, completeAudit } from '../lib/audit-log';
|
||||
@@ -511,9 +512,12 @@ export const WalletController = {
|
||||
}
|
||||
}
|
||||
|
||||
// C1, C2, H2 — sign-raw policy: `to` allowlist (Relay routers only) +
|
||||
// selector blacklist (approve/permit etc.) + value/gas caps. Throws on violation.
|
||||
// C1, C2, H2 — sign-raw policy: `to` allowlist (Relay routers — static + dynamic
|
||||
// cache из /relay/execute) + selector blacklist + value/gas caps.
|
||||
// Dynamic cache позволяет авто-trust'ить новые Relay router'ы которые юзер только
|
||||
// что увидел через /relay/execute (TTL 30 минут, set в Redis).
|
||||
try {
|
||||
const dynamicTrusted = await getRelayTrustedAddresses(Number(chainId));
|
||||
applyEvmTxPolicy({
|
||||
chainId: Number(chainId),
|
||||
to,
|
||||
@@ -521,6 +525,7 @@ export const WalletController = {
|
||||
value: String(value),
|
||||
gas: String(gas),
|
||||
maxFeePerGas: String(maxFeePerGas),
|
||||
dynamicTrusted,
|
||||
});
|
||||
} catch (policyErr: any) {
|
||||
res.status(400).json({ success: false, error: policyErr.message });
|
||||
@@ -768,13 +773,33 @@ export const WalletController = {
|
||||
return;
|
||||
}
|
||||
|
||||
const { transaction } = req.body ?? {};
|
||||
if (typeof transaction !== 'string' || transaction.length === 0 || transaction.length > 8192) {
|
||||
res.status(400).json({ success: false, error: 'Invalid transaction (must be non-empty base64 string ≤8KB)' });
|
||||
// Body может быть в одном из двух форматов:
|
||||
// A) { transaction: '<base64>' } — pre-built VersionedTransaction (от Jupiter / Relay /execute если они вернули сериализованную tx)
|
||||
// B) { instructions: [...], addressLookupTableAddresses?: [...] } — Relay SOL-bridge instructions (сервер сам compile'ит tx)
|
||||
const { transaction, instructions, addressLookupTableAddresses } = req.body ?? {};
|
||||
const hasTx = typeof transaction === 'string';
|
||||
const hasIxs = Array.isArray(instructions);
|
||||
|
||||
if (!hasTx && !hasIxs) {
|
||||
res.status(400).json({ success: false, error: 'Body must contain either {transaction:"<base64>"} or {instructions:[...]}' });
|
||||
return;
|
||||
}
|
||||
if (!/^[A-Za-z0-9+/=]+$/.test(transaction)) {
|
||||
res.status(400).json({ success: false, error: 'Invalid base64 transaction' });
|
||||
if (hasTx && hasIxs) {
|
||||
res.status(400).json({ success: false, error: 'Body must contain either transaction OR instructions, not both' });
|
||||
return;
|
||||
}
|
||||
if (hasTx) {
|
||||
if (transaction.length === 0 || transaction.length > 8192) {
|
||||
res.status(400).json({ success: false, error: 'Invalid transaction (must be non-empty base64 string ≤8KB)' });
|
||||
return;
|
||||
}
|
||||
if (!/^[A-Za-z0-9+/=]+$/.test(transaction)) {
|
||||
res.status(400).json({ success: false, error: 'Invalid base64 transaction' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (hasIxs && addressLookupTableAddresses !== undefined && !Array.isArray(addressLookupTableAddresses)) {
|
||||
res.status(400).json({ success: false, error: 'addressLookupTableAddresses must be an array of strings' });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -812,7 +837,9 @@ export const WalletController = {
|
||||
event: 'wallet.sign_sol_tx',
|
||||
userId,
|
||||
ip: req.ip || null,
|
||||
meta: { chain: 'SOL', txLength: transaction.length },
|
||||
meta: hasTx
|
||||
? { chain: 'SOL', mode: 'serialized', txLength: transaction.length }
|
||||
: { chain: 'SOL', mode: 'instructions', count: instructions.length },
|
||||
});
|
||||
} catch (auditErr: any) {
|
||||
logger.error(`Audit DB INSERT MUST succeed: ${auditErr.message}`);
|
||||
@@ -824,11 +851,20 @@ export const WalletController = {
|
||||
|
||||
let result: { signature: string };
|
||||
try {
|
||||
result = await signAndBroadcastSolanaTx({
|
||||
mnemonic,
|
||||
expectedFromAddress: wallet.address,
|
||||
serializedTransaction: transaction,
|
||||
});
|
||||
if (hasTx) {
|
||||
result = await signAndBroadcastSolanaTx({
|
||||
mnemonic,
|
||||
expectedFromAddress: wallet.address,
|
||||
serializedTransaction: transaction,
|
||||
});
|
||||
} else {
|
||||
result = await signAndBroadcastSolanaInstructions({
|
||||
mnemonic,
|
||||
expectedFromAddress: wallet.address,
|
||||
instructions,
|
||||
addressLookupTableAddresses,
|
||||
});
|
||||
}
|
||||
} catch (signErr: any) {
|
||||
await completeAudit(auditId, 'failure', undefined, 'SOL_SIGN_FAILED');
|
||||
throw signErr;
|
||||
|
||||
Reference in New Issue
Block a user