Операции с инвестициями (Investment Operations Endpoints - Integration-Only)¶
Аудитория: разработчики, frontend инженеры Последнее обновление: 2025-11-17 Архитектура: Integration-Only + Single Strategy MVP Краткое содержание: Упрощенная документация Investment endpoints для Single Strategy MVP (10% APY) — автоматическое создание инвестиций, просмотр производительности, operator-управляемые операции. Fordefi + Crypto2B интеграция для asset management.
Обзор endpoints (Single Strategy MVP)¶
User Investment Endpoints (3) - Simplified MVP¶
| Метод | Endpoint | Описание | Auth Required | MVP Status |
|---|---|---|---|---|
| POST | /api/user/investments |
Автосоздание инвестиции (single strategy 10% APY) | ✅ JWT | ✅ Core |
| GET | /api/user/investments |
Портфель пользователя (single strategy) | ✅ JWT | ✅ Core |
| GET | /api/user/investments/performance |
Производительность портфеля | ✅ JWT | ✅ Core |
Operator Investment Endpoints (4) - Simplified MVP¶
| Метод | Endpoint | Описание | Auth Required | MVP Status |
|---|---|---|---|---|
| GET | /api/admin/investments |
Список всех инвестиций (operator view) | ✅ Admin JWT | ✅ Core |
| GET | /api/admin/investments/:id |
Детали инвестиции | ✅ Admin JWT | ✅ Core |
| POST | /api/admin/investments/:id/close |
Частичное закрытие инвестиции | ✅ Admin JWT | ✅ Core |
| POST | /api/webhooks/fordefi/yield |
Webhook обновления доходности | ❌ No | ✅ Core |
🚫 Убрано из MVP: сложные операции (DELETE, bulk-approve, reject, cancel) упрощены до single strategy автоматического управления.
👤 USER INVESTMENT ENDPOINTS¶
💼 POST /api/user/investments (Single Strategy MVP)¶
Автоматическое создание инвестиции в единственной стратегии MVP (10% APY фиксированная доходность).
Запрос¶
POST /api/user/investments
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"strategyId": "balanced-10",
"amount": "1000.00",
"currency": "USDC",
"idempotencyKey": "uuid-123-abc-456"
}
Заголовки:
| Заголовок | Тип | Обязательно | Описание |
|---|---|---|---|
Authorization |
Bearer <token> |
✅ Да | User JWT токен |
Content-Type |
application/json |
✅ Да | Формат тела запроса |
Схема тела запроса:
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
strategyId |
string | ✅ Да | ID стратегии (conservative-5, balanced-10, aggressive-20) |
amount |
string | ✅ Да | Сумма инвестиции (SafeDecimal, минимум: 10 USDC) |
currency |
string | ❌ Нет | Валюта (по умолчанию: USDC) |
idempotencyKey |
string | ⚠️ Рекомендуется | UUID v4 для предотвращения дублей |
Правила валидации:
- Amount ≥ 10 USDC (минимальная инвестиция)
- Amount ≤ доступный баланс пользователя
- Стратегия должна быть активна
- Баланс проверяется перед обработкой
Ответ¶
Успех (201 Created):
{
"success": true,
"data": {
"id": "inv_abc123",
"userId": "user_123",
"strategyId": "balanced-10",
"strategyName": "Balanced 10% APY Strategy",
"amount": "1000.00",
"currency": "USDC",
"apy": 10.0,
"status": "active",
"allocation": {
"pendle": "300.00",
"curve": "400.00",
"convex": "300.00"
},
"performance": {
"currentValue": "1000.00",
"totalEarnings": "0.00",
"roi": 0.0,
"dailyYield": "0.00"
},
"createdAt": "2025-10-06T12:34:56Z",
"lastUpdated": "2025-10-06T12:34:56Z",
"nextYieldDate": "2025-10-07T00:00:00Z"
},
"timestamp": "2025-10-06T12:34:56Z"
}
Ошибки:
| Код | Код ошибки | Описание |
|---|---|---|
| 400 | INVALID_AMOUNT |
Amount < 10 USDC |
| 422 | INSUFFICIENT_BALANCE |
Баланс < сумма |
| 422 | STRATEGY_NOT_AVAILABLE |
Стратегия недоступна |
| 409 | DUPLICATE_INVESTMENT |
Idempotency key использован |
Пример cURL¶
TOKEN="eyJhbGciOiJIUzI1NiIs..."
curl -X POST https://app.saga.surf/api/user/investments \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"strategyId": "balanced-10",
"amount": "1000.00",
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440000"
}'
Пример TypeScript¶
import { v4 as uuidv4 } from 'uuid';
const createInvestment = async (strategyId: string, amount: string) => {
const response = await fetch('/api/user/investments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
strategyId,
amount,
idempotencyKey: uuidv4()
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error.message);
}
const { data: investment } = await response.json();
console.log('Инвестиция создана:', investment.id);
return investment;
};
await createInvestment('balanced-10', '1000.00');
GET /api/user/investments¶
Получить список всех инвестиций пользователя с фильтрацией и pagination.
Запрос¶
GET /api/user/investments?status=active&strategyId=balanced-10&page=1&limit=20
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Query параметры:
| Параметр | Тип | Описание | Default |
|---|---|---|---|
page |
number | Номер страницы | 1 |
limit |
number | Элементов на странице (макс: 100) | 20 |
status |
string | Фильтр: active, paused, closed |
Все |
strategyId |
string | Фильтр по стратегии | Все |
sortBy |
string | Поле: createdAt, amount, currentValue |
createdAt |
orderBy |
string | Порядок: asc, desc |
desc |
Ответ¶
Успех (200 OK):
{
"success": true,
"data": [
{
"id": "inv_abc123",
"strategyId": "balanced-10",
"strategyName": "Balanced 10% APY Strategy",
"amount": "1000.00",
"currentValue": "1050.25",
"totalEarnings": "50.25",
"apy": 10.0,
"roi": 5.03,
"status": "active",
"createdAt": "2025-10-01T10:00:00Z",
"nextYieldDate": "2025-10-07T00:00:00Z"
}
],
"summary": {
"totalInvested": "1000.00",
"totalCurrentValue": "1050.25",
"totalEarnings": "50.25",
"averageROI": 5.03,
"activeInvestments": 1
},
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1,
"hasNext": false,
"hasPrevious": false
},
"timestamp": "2025-10-06T12:34:56Z"
}
Пример TypeScript¶
const fetchInvestments = async (filters = {}) => {
const params = new URLSearchParams({
page: '1',
limit: '20',
...filters
});
const response = await fetch(`/api/user/investments?${params}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const { data: investments, summary } = await response.json();
console.log('Портфель:', summary.totalCurrentValue, 'USDC');
console.log('Доходность:', summary.totalEarnings, 'USDC');
return { investments, summary };
};
await fetchInvestments({ status: 'active' });
GET /api/user/investments/:id¶
Получить детальную информацию о конкретной инвестиции.
Запрос¶
Path параметры:
| Параметр | Тип | Описание |
|---|---|---|
id |
string | ID инвестиции |
Ответ¶
Успех (200 OK):
{
"success": true,
"data": {
"id": "inv_abc123",
"userId": "user_123",
"strategyId": "balanced-10",
"strategyName": "Balanced 10% APY Strategy",
"amount": "1000.00",
"currency": "USDC",
"apy": 10.0,
"status": "active",
"allocation": {
"pendle": {
"amount": "300.00",
"percentage": 30.0,
"protocol": "Pendle Finance",
"currentValue": "307.50"
},
"curve": {
"amount": "400.00",
"percentage": 40.0,
"protocol": "Curve 3pool",
"currentValue": "410.00"
},
"convex": {
"amount": "300.00",
"percentage": 30.0,
"protocol": "Convex Boost",
"currentValue": "332.75"
}
},
"performance": {
"currentValue": "1050.25",
"totalEarnings": "50.25",
"roi": 5.03,
"dailyYield": "2.74",
"projectedAnnualReturn": "100.00"
},
"createdAt": "2025-10-01T10:00:00Z",
"nextYieldDate": "2025-10-07T00:00:00Z"
},
"timestamp": "2025-10-06T12:34:56Z"
}
Ошибки:
| Код | Код ошибки | Описание |
|---|---|---|
| 403 | PERMISSION_DENIED |
Чужая инвестиция |
| 404 | INVESTMENT_NOT_FOUND |
ID не существует |
DELETE /api/user/investments/:id¶
Удалить инвестицию (вывод всех средств на баланс).
Запрос¶
Ответ¶
Успех (200 OK):
{
"success": true,
"data": {
"investmentId": "inv_abc123",
"status": "closed",
"finalValue": "1050.25",
"initialInvestment": "1000.00",
"totalEarnings": "50.25",
"roi": 5.03,
"withdrawnAmount": "1050.25",
"closedAt": "2025-10-06T12:34:56Z",
"fees": {
"managementFee": "2.51",
"gasFee": "0.005 ETH"
}
},
"timestamp": "2025-10-06T12:34:56Z"
}
🚫 POST /api/user/investments/:id/cancel¶
Отменить инвестицию (альтернативный метод к DELETE).
Запрос¶
Path параметры:
| Параметр | Тип | Описание |
|---|---|---|
id |
string | ID инвестиции для отмены |
Ответ¶
Успех (200 OK):
{
"success": true,
"data": {
"id": "inv_abc123",
"status": "cancelled",
"message": "Investment cancelled successfully"
},
"timestamp": "2025-10-06T12:34:56Z"
}
Ошибки:
| Код | Код ошибки | Описание |
|---|---|---|
| 403 | PERMISSION_DENIED |
Чужая инвестиция |
| 404 | INVESTMENT_NOT_FOUND |
ID не существует |
| 422 | INVESTMENT_ALREADY_CLOSED |
Уже закрыта |
Пример TypeScript¶
const cancelInvestment = async (investmentId: string) => {
const response = await fetch(`/api/user/investments/${investmentId}/cancel`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
}
});
const { data } = await response.json();
console.log('Инвестиция отменена:', data.id);
console.log('Статус:', data.status);
return data;
};
await cancelInvestment('inv_abc123');
GET /api/user/investments/performance¶
Получить производительность всего инвестиционного портфеля.
Запрос¶
Заголовки:
| Заголовок | Значение | Обязательно |
|---|---|---|
Authorization |
Bearer <token> |
✅ Да |
Ответ¶
Успех (200 OK):
{
"success": true,
"data": {
"portfolio": {
"totalInvested": "5000.00",
"totalCurrentValue": "5425.80",
"totalEarnings": "425.80",
"totalReturn": "425.80",
"averageROI": 8.52,
"weightedAPY": 12.5
},
"investments": [
{
"strategyId": "aggressive-20",
"strategyName": "Aggressive 20% APY",
"totalInvested": "2000.00",
"currentValue": "2200.00",
"earnings": "200.00",
"roi": 10.0,
"count": 1
},
{
"strategyId": "balanced-10",
"strategyName": "Balanced 10% APY",
"totalInvested": "2000.00",
"currentValue": "2150.00",
"earnings": "150.00",
"roi": 7.5,
"count": 2
},
{
"strategyId": "conservative-5",
"strategyName": "Conservative 5% APY",
"totalInvested": "1000.00",
"currentValue": "1075.80",
"earnings": "75.80",
"roi": 7.58,
"count": 1
}
],
"breakdown": {
"byStatus": {
"active": 4,
"paused": 0,
"closed": 1
},
"byStrategy": {
"aggressive-20": 1,
"balanced-10": 2,
"conservative-5": 1
}
}
},
"timestamp": "2025-10-06T12:34:56Z"
}
Поля ответа:
| Поле | Тип | Описание |
|---|---|---|
portfolio.totalInvested |
string | Общая сумма всех инвестиций |
portfolio.totalCurrentValue |
string | Текущая стоимость портфеля |
portfolio.totalEarnings |
string | Общая доходность |
portfolio.totalReturn |
string | Общий доход (earnings) |
portfolio.averageROI |
number | Средневзвешенный ROI |
portfolio.weightedAPY |
number | Взвешенный APY портфеля |
investments |
array | Breakdown по стратегиям |
breakdown |
object | Статистика по статусам и стратегиям |
Ошибки:
| Код | Код ошибки | Описание |
|---|---|---|
| 401 | UNAUTHORIZED |
Токен невалиден |
| 500 | INTERNAL_ERROR |
Ошибка получения данных |
Пример cURL¶
TOKEN="eyJhbGciOiJIUzI1NiIs..."
curl -X GET https://app.saga.surf/api/user/investments/performance \
-H "Authorization: Bearer $TOKEN"
Пример TypeScript¶
const getPortfolioPerformance = async () => {
const response = await fetch('/api/user/investments/performance', {
headers: {
'Authorization': `Bearer ${token}`
}
});
const { data } = await response.json();
console.log('=== PORTFOLIO PERFORMANCE ===');
console.log('Total Invested:', data.portfolio.totalInvested, 'USDC');
console.log('Current Value:', data.portfolio.totalCurrentValue, 'USDC');
console.log('Total Earnings:', data.portfolio.totalEarnings, 'USDC');
console.log('Average ROI:', data.portfolio.averageROI, '%');
console.log('Weighted APY:', data.portfolio.weightedAPY, '%');
console.log('\n=== BREAKDOWN BY STRATEGY ===');
data.investments.forEach(strategy => {
console.log(`${strategy.strategyName}:`);
console.log(` Invested: ${strategy.totalInvested} USDC`);
console.log(` Current: ${strategy.currentValue} USDC`);
console.log(` Earnings: ${strategy.earnings} USDC (${strategy.roi}%)`);
console.log(` Count: ${strategy.count} investments`);
});
return data;
};
await getPortfolioPerformance();
React Hook для Portfolio Performance¶
import { useState, useEffect } from 'react';
interface PortfolioPerformance {
portfolio: {
totalInvested: string;
totalCurrentValue: string;
totalEarnings: string;
averageROI: number;
weightedAPY: number;
};
investments: Array<{
strategyId: string;
strategyName: string;
totalInvested: string;
currentValue: string;
earnings: string;
roi: number;
count: number;
}>;
breakdown: {
byStatus: Record<string, number>;
byStrategy: Record<string, number>;
};
}
export const usePortfolioPerformance = () => {
const [performance, setPerformance] = useState<PortfolioPerformance | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchPerformance = async () => {
try {
const token = localStorage.getItem('saga_access_token');
const response = await fetch('/api/user/investments/performance', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch portfolio performance');
}
const { data } = await response.json();
setPerformance(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
fetchPerformance();
}, []);
return { performance, loading, error };
};
// Usage in component:
// const { performance, loading, error } = usePortfolioPerformance();
ADMIN INVESTMENT ENDPOINTS¶
GET /api/admin/investments/queue¶
Получить очередь pending инвестиций для одобрения администратором.
Запрос¶
Заголовки:
| Заголовок | Значение | Обязательно |
|---|---|---|
Authorization |
Bearer <admin_token> |
✅ Да |
Ответ¶
Успех (200 OK):
{
"success": true,
"data": {
"pendingInvestments": [
{
"investmentId": "inv_pending123",
"userId": "user_456",
"userEmail": "investor@example.com",
"strategy": "Balanced 10% APY Strategy",
"amount": "5000.00",
"currency": "USDC",
"createdAt": "2025-10-06T10:30:00Z",
"priority": "50.00"
},
{
"investmentId": "inv_pending124",
"userId": "user_789",
"userEmail": "trader@example.com",
"strategy": "Aggressive 20% APY Strategy",
"amount": "10000.00",
"currency": "USDC",
"createdAt": "2025-10-06T09:15:00Z",
"priority": "100.00"
}
],
"total": 2,
"queueSize": 2
},
"timestamp": "2025-10-06T12:34:56Z"
}
Поля ответа:
| Поле | Тип | Описание |
|---|---|---|
pendingInvestments |
array | Список pending инвестиций |
pendingInvestments[].priority |
string | Приоритет (рассчитывается по сумме) |
total |
number | Всего pending инвестиций |
queueSize |
number | Размер очереди |
Ошибки:
| Код | Код ошибки | Описание |
|---|---|---|
| 401 | UNAUTHORIZED |
Не admin токен |
| 403 | ADMIN_REQUIRED |
Требуется admin роль |
Пример cURL¶
ADMIN_TOKEN="eyJhbGciOiJIUzI1NiIs..."
curl -X GET https://admin.saga.surf/api/admin/investments/queue \
-H "Authorization: Bearer $ADMIN_TOKEN"
Пример TypeScript¶
const getInvestmentQueue = async () => {
const adminToken = localStorage.getItem('saga_admin_token');
const response = await fetch('/api/admin/investments/queue', {
headers: {
'Authorization': `Bearer ${adminToken}`
}
});
const { data } = await response.json();
console.log(`${data.queueSize} инвестиций в очереди:`);
data.pendingInvestments.forEach(inv => {
console.log(`- ${inv.userEmail}: ${inv.amount} USDC (${inv.strategy})`);
console.log(` Приоритет: ${inv.priority}, Создано: ${inv.createdAt}`);
});
return data;
};
await getInvestmentQueue();
POST /api/admin/investments/bulk-approve¶
Массовое одобрение нескольких инвестиций одновременно.
Запрос¶
POST /api/admin/investments/bulk-approve
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"investmentIds": [
"inv_pending123",
"inv_pending124",
"inv_pending125"
]
}
Схема тела запроса:
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
investmentIds |
array | ✅ Да | Массив ID инвестиций для одобрения |
Ответ¶
Успех (200 OK):
{
"success": true,
"data": {
"approved": [
{
"investmentId": "inv_pending123",
"status": "approved",
"approvedAt": "2025-10-06T12:34:56Z"
},
{
"investmentId": "inv_pending124",
"status": "approved",
"approvedAt": "2025-10-06T12:34:56Z"
}
],
"failed": [
{
"investmentId": "inv_pending125",
"error": "Investment already approved",
"code": "ALREADY_APPROVED"
}
],
"summary": {
"total": 3,
"approved": 2,
"failed": 1
}
},
"timestamp": "2025-10-06T12:34:56Z"
}
Ошибки:
| Код | Код ошибки | Описание |
|---|---|---|
| 400 | INVALID_REQUEST |
Пустой массив investmentIds |
| 401 | UNAUTHORIZED |
Не admin токен |
Пример TypeScript¶
const bulkApproveInvestments = async (investmentIds: string[]) => {
const adminToken = localStorage.getItem('saga_admin_token');
const response = await fetch('/api/admin/investments/bulk-approve', {
method: 'POST',
headers: {
'Authorization': `Bearer ${adminToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ investmentIds })
});
const { data } = await response.json();
console.log(`Одобрено: ${data.summary.approved}/${data.summary.total}`);
if (data.failed.length > 0) {
console.log('Ошибки:');
data.failed.forEach(f => {
console.log(`- ${f.investmentId}: ${f.error}`);
});
}
return data;
};
await bulkApproveInvestments(['inv_pending123', 'inv_pending124']);
POST /api/admin/investments/:id/approve¶
Одобрить конкретную инвестицию.
Запрос¶
Path параметры:
| Параметр | Тип | Описание |
|---|---|---|
id |
string | ID инвестиции для одобрения |
Ответ¶
Успех (200 OK):
{
"success": true,
"data": {
"investmentId": "inv_pending123",
"status": "approved",
"approvedAt": "2025-10-06T12:34:56Z",
"approvedBy": "admin_789",
"previousStatus": "pending"
},
"timestamp": "2025-10-06T12:34:56Z"
}
Ошибки:
| Код | Код ошибки | Описание |
|---|---|---|
| 404 | INVESTMENT_NOT_FOUND |
ID не существует |
| 422 | ALREADY_APPROVED |
Уже одобрена |
POST /api/admin/investments/:id/reject¶
Отклонить инвестицию.
Запрос¶
POST /api/admin/investments/inv_pending123/reject
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"reason": "Недостаточная документация KYC"
}
Схема тела запроса:
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
reason |
string | ⚠️ Рекомендуется | Причина отклонения |
Ответ¶
Успех (200 OK):
{
"success": true,
"data": {
"investmentId": "inv_pending123",
"status": "rejected",
"rejectedAt": "2025-10-06T12:34:56Z",
"rejectedBy": "admin_789",
"reason": "Недостаточная документация KYC"
},
"timestamp": "2025-10-06T12:34:56Z"
}
POST /api/admin/investments/:id/close¶
Закрыть инвестицию (admin принудительное закрытие).
Запрос¶
POST /api/admin/investments/inv_abc123/close
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"reason": "Стратегия больше не поддерживается"
}
Схема тела запроса:
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
reason |
string | ✅ Да | Причина закрытия |
Ответ¶
Успех (200 OK):
{
"success": true,
"data": {
"investmentId": "inv_abc123",
"status": "closed",
"closedAt": "2025-10-06T12:34:56Z",
"closedBy": "admin_789",
"finalValue": "1050.25",
"reason": "Стратегия больше не поддерживается",
"fundsReturned": true
},
"timestamp": "2025-10-06T12:34:56Z"
}
PUT /api/admin/investments/:id¶
Обновить параметры инвестиции.
Запрос¶
PUT /api/admin/investments/inv_abc123
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"strategyId": "aggressive-20",
"amount": "1200.00"
}
Схема тела запроса:
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
strategyId |
string | ❌ Нет | Новая стратегия |
amount |
string | ❌ Нет | Новая сумма |
status |
string | ❌ Нет | Новый статус |
Ответ¶
Успех (200 OK):
{
"success": true,
"data": {
"id": "inv_abc123",
"strategyId": "aggressive-20",
"amount": "1200.00",
"status": "active",
"updatedAt": "2025-10-06T12:34:56Z",
"updatedBy": "admin_789"
},
"timestamp": "2025-10-06T12:34:56Z"
}
GET /api/admin/investments/:id¶
Получить детали инвестиции (admin view с расширенной информацией).
Запрос¶
Ответ¶
Успех (200 OK):
{
"success": true,
"data": {
"id": "inv_abc123",
"userId": "user_456",
"userEmail": "investor@example.com",
"strategyId": "balanced-10",
"strategyName": "Balanced 10% APY Strategy",
"amount": "1000.00",
"status": "active",
"createdAt": "2025-10-01T10:00:00Z",
"approvedAt": "2025-10-01T10:30:00Z",
"approvedBy": "admin_123",
"performance": {
"currentValue": "1050.25",
"totalEarnings": "50.25",
"roi": 5.03
},
"adminMetadata": {
"riskScore": 3.5,
"compliance": "verified",
"lastAudit": "2025-10-05T14:00:00Z"
}
},
"timestamp": "2025-10-06T12:34:56Z"
}
GET /api/admin/investments¶
Список всех инвестиций с расширенными admin фильтрами.
Запрос¶
GET /api/admin/investments?status=pending&userId=user_456&page=1&limit=50
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Query параметры:
| Параметр | Тип | Описание | Default |
|---|---|---|---|
page |
number | Номер страницы | 1 |
limit |
number | Элементов на странице | 50 |
status |
string | Фильтр по статусу | Все |
userId |
string | Фильтр по пользователю | Все |
strategyId |
string | Фильтр по стратегии | Все |
sortBy |
string | Поле сортировки | createdAt |
sortDir |
string | Направление: asc, desc |
desc |
Ответ¶
Успех (200 OK):
{
"success": true,
"data": {
"investments": [
{
"id": "inv_abc123",
"userId": "user_456",
"userEmail": "investor@example.com",
"strategyId": "balanced-10",
"strategyName": "Balanced 10% APY Strategy",
"amount": "1000.00",
"status": "active",
"createdAt": "2025-10-01T10:00:00Z"
}
],
"total": 1,
"pagination": {
"page": 1,
"limit": 50,
"totalPages": 1
}
},
"timestamp": "2025-10-06T12:34:56Z"
}
💼 POST /api/admin/investments¶
Создать инвестицию от имени пользователя (admin operation).
Запрос¶
POST /api/admin/investments
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"userId": "user_456",
"strategyId": "balanced-10",
"amount": "1000.00",
"autoApprove": true
}
Схема тела запроса:
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
userId |
string | ✅ Да | ID пользователя |
strategyId |
string | ✅ Да | ID стратегии |
amount |
string | ✅ Да | Сумма инвестиции |
autoApprove |
boolean | ❌ Нет | Автоматическое одобрение (default: false) |
Ответ¶
Успех (201 Created):
{
"success": true,
"data": {
"id": "inv_admin_created_123",
"userId": "user_456",
"strategyId": "balanced-10",
"amount": "1000.00",
"status": "approved",
"createdAt": "2025-10-06T12:34:56Z",
"createdBy": "admin_789",
"autoApproved": true
},
"timestamp": "2025-10-06T12:34:56Z"
}
Use Cases¶
Use Case 1: Complete Investment Flow (User)¶
import { v4 as uuidv4 } from 'uuid';
async function completeInvestmentFlow() {
// 1. Проверить портфель
const { performance } = await fetch('/api/user/investments/performance', {
headers: { 'Authorization': `Bearer ${token}` }
}).then(r => r.json());
console.log('Текущий портфель:', performance.portfolio.totalCurrentValue);
// 2. Создать новую инвестицию
const newInvestment = await fetch('/api/user/investments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
strategyId: 'balanced-10',
amount: '1000.00',
idempotencyKey: uuidv4()
})
}).then(r => r.json());
console.log('Инвестиция создана:', newInvestment.data.id);
// 3. Получить детали
const details = await fetch(`/api/user/investments/${newInvestment.data.id}`, {
headers: { 'Authorization': `Bearer ${token}` }
}).then(r => r.json());
console.log('Allocation:', details.data.allocation);
// 4. При необходимости отменить
if (needCancel) {
await fetch(`/api/user/investments/${newInvestment.data.id}/cancel`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
});
}
}
Use Case 2: Admin Investment Queue Management¶
async function manageInvestmentQueue() {
const adminToken = localStorage.getItem('saga_admin_token');
// 1. Получить очередь
const queue = await fetch('/api/admin/investments/queue', {
headers: { 'Authorization': `Bearer ${adminToken}` }
}).then(r => r.json());
console.log(`${queue.data.queueSize} pending инвестиций`);
// 2. Bulk approve высокоприоритетных
const highPriority = queue.data.pendingInvestments
.filter(inv => parseFloat(inv.priority) >= 75)
.map(inv => inv.investmentId);
if (highPriority.length > 0) {
const result = await fetch('/api/admin/investments/bulk-approve', {
method: 'POST',
headers: {
'Authorization': `Bearer ${adminToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ investmentIds: highPriority })
}).then(r => r.json());
console.log(`Approved: ${result.data.summary.approved}`);
}
// 3. Reject низкоприоритетных
const lowPriority = queue.data.pendingInvestments
.filter(inv => parseFloat(inv.priority) < 25);
for (const inv of lowPriority) {
await fetch(`/api/admin/investments/${inv.investmentId}/reject`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${adminToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
reason: 'Низкий приоритет - требуется дополнительная проверка'
})
});
}
}
Use Case 3: Portfolio Dashboard Component¶
import { usePortfolioPerformance } from './hooks/usePortfolioPerformance';
import { Loader, AlertCircle } from 'lucide-react';
export const PortfolioDashboard = () => {
const { performance, loading, error } = usePortfolioPerformance();
if (loading) return <Loader className="animate-spin" />;
if (error) return <AlertCircle className="text-red-500" />;
if (!performance) return null;
const { portfolio, investments } = performance;
return (
<div className="space-y-6">
<div className="grid grid-cols-3 gap-4">
<Card>
<h3>Total Invested</h3>
<p className="text-2xl font-bold">{portfolio.totalInvested} USDC</p>
</Card>
<Card>
<h3>Current Value</h3>
<p className="text-2xl font-bold">{portfolio.totalCurrentValue} USDC</p>
</Card>
<Card>
<h3>Total Earnings</h3>
<p className="text-2xl font-bold text-green-600">
+{portfolio.totalEarnings} USDC ({portfolio.averageROI}%)
</p>
</Card>
</div>
<div>
<h3 className="text-lg font-semibold mb-4">Breakdown by Strategy</h3>
{investments.map(strategy => (
<div key={strategy.strategyId} className="border-b py-3">
<div className="flex justify-between items-center">
<div>
<p className="font-medium">{strategy.strategyName}</p>
<p className="text-sm text-gray-500">
{strategy.count} {strategy.count === 1 ? 'investment' : 'investments'}
</p>
</div>
<div className="text-right">
<p className="font-semibold">{strategy.currentValue} USDC</p>
<p className="text-sm text-green-600">
+{strategy.earnings} USDC ({strategy.roi}%)
</p>
</div>
</div>
</div>
))}
</div>
</div>
);
};
Use Case 4: Admin Investment Management Panel¶
import { useState, useEffect } from 'react';
export const AdminInvestmentPanel = () => {
const [queue, setQueue] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchQueue();
}, []);
const fetchQueue = async () => {
const adminToken = localStorage.getItem('saga_admin_token');
const response = await fetch('/api/admin/investments/queue', {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
const { data } = await response.json();
setQueue(data.pendingInvestments);
setLoading(false);
};
const handleApprove = async (investmentId: string) => {
const adminToken = localStorage.getItem('saga_admin_token');
await fetch(`/api/admin/investments/${investmentId}/approve`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${adminToken}` }
});
fetchQueue(); // Refresh
};
const handleReject = async (investmentId: string, reason: string) => {
const adminToken = localStorage.getItem('saga_admin_token');
await fetch(`/api/admin/investments/${investmentId}/reject`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${adminToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ reason })
});
fetchQueue(); // Refresh
};
if (loading) return <Loader />;
return (
<div>
<h2 className="text-xl font-bold mb-4">
Investment Queue ({queue.length} pending)
</h2>
<div className="space-y-3">
{queue.map(inv => (
<div key={inv.investmentId} className="border p-4 rounded">
<div className="flex justify-between items-start mb-2">
<div>
<p className="font-medium">{inv.userEmail}</p>
<p className="text-sm text-gray-600">{inv.strategy}</p>
</div>
<div className="text-right">
<p className="font-bold">{inv.amount} USDC</p>
<p className="text-xs text-gray-500">
Priority: {inv.priority}
</p>
</div>
</div>
<div className="flex gap-2 mt-3">
<button
onClick={() => handleApprove(inv.investmentId)}
className="px-4 py-2 bg-green-600 text-white rounded"
>
Approve
</button>
<button
onClick={() => {
const reason = prompt('Причина отклонения:');
if (reason) handleReject(inv.investmentId, reason);
}}
className="px-4 py-2 bg-red-600 text-white rounded"
>
Reject
</button>
</div>
</div>
))}
</div>
</div>
);
};
Use Case 5: Investment Cancellation with Confirmation¶
async function cancelInvestmentWithConfirmation(investmentId: string) {
// 1. Получить детали для подтверждения
const details = await fetch(`/api/user/investments/${investmentId}`, {
headers: { 'Authorization': `Bearer ${token}` }
}).then(r => r.json());
const { data: investment } = details;
// 2. Показать confirmation dialog
const confirmed = confirm(`
Отменить инвестицию?
Стратегия: ${investment.strategyName}
Начальная сумма: ${investment.amount} USDC
Текущая стоимость: ${investment.performance.currentValue} USDC
Доходность: ${investment.performance.totalEarnings} USDC
Средства будут возвращены на ваш баланс.
`);
if (!confirmed) {
console.log('Отмена отменена пользователем');
return;
}
// 3. Выполнить отмену
const result = await fetch(`/api/user/investments/${investmentId}/cancel`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
}).then(r => r.json());
console.log('Инвестиция отменена:', result.data);
// 4. Обновить UI - показать новый баланс
const balance = await fetch('/api/user/balance', {
headers: { 'Authorization': `Bearer ${token}` }
}).then(r => r.json());
console.log('Новый баланс:', balance.data.availableBalance);
return result.data;
}
Связанная документация¶
Другие User Endpoints:
- Стратегии - Доступные инвестиционные стратегии
- Баланс - Endpoints баланса и транзакций
- Выводы - Операции вывода средств
- Аутентификация - JWT токены для user/admin
Admin Documentation:
- Admin Dashboard - Обзор админской панели
- Admin Auth - Админская аутентификация
Руководства:
- Strategy Selection Guide - Помощь пользователям в выборе стратегии
Архитектура:
- Protocol Integration - Детали Pendle/Curve/Convex
Technical Details¶
Investment Status State Machine¶
Статусы:
pending- Ожидает одобрения админаapproved- Одобрена админомactive- Активна и зарабатывает доходностьpaused- Временно приостановленаclosed- Закрыта (средства выведены)rejected- Отклонена админом
Priority Calculation¶
Приоритет инвестиций в admin queue рассчитывается по формуле:
Диапазоны:
- 0-25: Низкий приоритет
- 26-50: Средний приоритет
- 51-75: Высокий приоритет
- 76-100: Критический приоритет
Performance Metrics¶
ROI (Return on Investment):
Weighted APY:
Daily Yield (projected):
Security Best Practices¶
Frontend:
- Всегда валидировать суммы перед отправкой
- Использовать idempotency keys для создания инвестиций
- Показывать confirmation dialogs для критичных операций (cancel, delete)
- Хранить admin токены отдельно от user токенов
- Implement automatic token refresh для бесшовного UX
Backend:
- Investment amounts валидируются через SafeDecimal
- Admin операции требуют дополнительной авторизации
- Bulk operations имеют rate limiting
- Все investment state changes логируются в audit trail
- Защита от race conditions через database transactions
📋 Метаданные¶
Версия: 2.6.268
Обновлено: 2025-11-14
Статус: Ready for Implementation
Архитектура: Single Strategy (10% APY), Fordefi + Crypto2B Asset Management
"Single Strategy MVP: единственная инвестиционная опция, автоматическое создание, external asset management"
— Saga Development Team (Phase 2 API Updates)