init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
This commit is contained in:
16
webapp/lib/api/contracts.ts
Normal file
16
webapp/lib/api/contracts.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface ContractConfig {
|
||||
name: string
|
||||
chain_id: number
|
||||
address: string
|
||||
}
|
||||
|
||||
export async function fetchContracts(): Promise<ContractConfig[]> {
|
||||
try {
|
||||
const res = await fetch('/api/contracts')
|
||||
if (!res.ok) return []
|
||||
const data = await res.json()
|
||||
return data.contracts ?? []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
243
webapp/lib/api/fundmarket.ts
Normal file
243
webapp/lib/api/fundmarket.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
// Fund Market API Client
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080/api';
|
||||
|
||||
export interface StatsData {
|
||||
label: string;
|
||||
value: string;
|
||||
change: string;
|
||||
isPositive: boolean;
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
id: number;
|
||||
name: string;
|
||||
tokenSymbol: string;
|
||||
decimals: number;
|
||||
contractAddress: string;
|
||||
chainId: number;
|
||||
token_role: string;
|
||||
category: string;
|
||||
categoryColor: "blue" | "green" | "orange" | "purple" | "red";
|
||||
iconUrl: string;
|
||||
yieldAPY: string;
|
||||
poolCap: string;
|
||||
risk: string;
|
||||
riskLevel: 1 | 2 | 3;
|
||||
circulatingSupply: string;
|
||||
poolCapacityPercent: number;
|
||||
}
|
||||
|
||||
// Fetch all products
|
||||
// tokenList=true: include stablecoins (for token dropdowns)
|
||||
// tokenList=false (default): exclude stablecoins (Fund Market page)
|
||||
export async function fetchProducts(tokenList: boolean = false): Promise<Product[]> {
|
||||
try {
|
||||
const url = tokenList === true
|
||||
? `${API_BASE_URL}/fundmarket/products?token_list=1`
|
||||
: `${API_BASE_URL}/fundmarket/products`;
|
||||
const response = await fetch(url, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch products');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to fetch products');
|
||||
}
|
||||
|
||||
return data.data ?? [];
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching products:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch market stats
|
||||
export async function fetchStats(): Promise<StatsData[]> {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/fundmarket/stats`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch stats');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to fetch stats');
|
||||
}
|
||||
|
||||
return data.data;
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching stats:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Product Detail Interfaces
|
||||
export interface ProductDetail {
|
||||
// Basic Info
|
||||
id: number;
|
||||
assetCode: string;
|
||||
name: string;
|
||||
subtitle: string;
|
||||
description: string;
|
||||
tokenSymbol: string;
|
||||
decimals: number;
|
||||
|
||||
// Investment Parameters
|
||||
underlyingAssets: string;
|
||||
poolCapUsd: number;
|
||||
riskLevel: number;
|
||||
riskLabel: string;
|
||||
targetApy: number;
|
||||
|
||||
// Contract Info
|
||||
contractAddress: string;
|
||||
chainId: number;
|
||||
|
||||
// Display Info
|
||||
category: string;
|
||||
categoryColor: string;
|
||||
iconUrl: string;
|
||||
|
||||
// Performance Data
|
||||
currentApy: number;
|
||||
tvlUsd: number;
|
||||
volume24hUsd: number;
|
||||
volumeChangeVsAvg: number;
|
||||
circulatingSupply: number;
|
||||
poolCapacityPercent: number;
|
||||
currentPrice: number;
|
||||
|
||||
// Custody Info
|
||||
custody?: {
|
||||
custodianName: string;
|
||||
custodyType: string;
|
||||
custodyLocation: string;
|
||||
auditorName: string;
|
||||
lastAuditDate: string;
|
||||
auditReportUrl?: string;
|
||||
additionalInfo?: any;
|
||||
};
|
||||
|
||||
// Audit Reports
|
||||
auditReports: Array<{
|
||||
reportType: string;
|
||||
reportTitle: string;
|
||||
reportDate: string;
|
||||
auditorName: string;
|
||||
summary: string;
|
||||
reportUrl: string;
|
||||
}>;
|
||||
|
||||
// Product Links
|
||||
productLinks: Array<{
|
||||
linkText: string;
|
||||
linkUrl: string;
|
||||
description: string;
|
||||
displayArea: string; // 'protocol' | 'verification' | 'both'
|
||||
displayOrder: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
// Fetch single product by ID (simple format)
|
||||
export async function fetchProductById(id: number): Promise<Product | null> {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/fundmarket/products/${id}`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch product');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to fetch product');
|
||||
}
|
||||
|
||||
return data.data;
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching product:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Daily return point
|
||||
export interface DailyReturnPoint {
|
||||
date: string; // "YYYY-MM-DD"
|
||||
ytPrice: number;
|
||||
dailyReturn: number; // %
|
||||
hasData: boolean;
|
||||
}
|
||||
|
||||
export async function fetchDailyReturns(id: number, year: number, month: number): Promise<DailyReturnPoint[]> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE_URL}/fundmarket/products/${id}/daily-returns?year=${year}&month=${month}`,
|
||||
{ cache: 'no-store' }
|
||||
);
|
||||
if (!response.ok) throw new Error('Failed to fetch daily returns');
|
||||
const data = await response.json();
|
||||
if (!data.success) throw new Error(data.error || 'Failed to fetch daily returns');
|
||||
return data.data ?? [];
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching daily returns:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// History point for APY/Price chart
|
||||
export interface HistoryPoint {
|
||||
time: string; // "MM/DD HH:mm"
|
||||
apy: number; // APY %
|
||||
price: number; // YT token price in USDC
|
||||
}
|
||||
|
||||
// Fetch hourly APY/price history for chart
|
||||
export async function fetchProductHistory(id: number): Promise<HistoryPoint[]> {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/fundmarket/products/${id}/history`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!response.ok) throw new Error('Failed to fetch history');
|
||||
const data = await response.json();
|
||||
if (!data.success) throw new Error(data.error || 'Failed to fetch history');
|
||||
return data.data ?? [];
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching product history:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch product detail by ID (detailed format)
|
||||
export async function fetchProductDetail(id: number): Promise<ProductDetail | null> {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/fundmarket/products/${id}`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch product detail');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to fetch product detail');
|
||||
}
|
||||
|
||||
return data.data;
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching product detail:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
201
webapp/lib/api/lending.ts
Normal file
201
webapp/lib/api/lending.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
// Lending API Client
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080/api';
|
||||
|
||||
export interface CollateralInfo {
|
||||
tokenSymbol: string;
|
||||
balance: string;
|
||||
balanceUsd: number;
|
||||
collateralValue: number;
|
||||
}
|
||||
|
||||
export interface LendingPosition {
|
||||
userAddress: string;
|
||||
walletAddress: string;
|
||||
suppliedBalance: string;
|
||||
suppliedBalanceUsd: number;
|
||||
borrowedBalance: string;
|
||||
borrowedBalanceUsd: number;
|
||||
collateralBalances: Record<string, CollateralInfo>;
|
||||
healthFactor: number;
|
||||
ltv: number;
|
||||
supplyAPY: number;
|
||||
borrowAPY: number;
|
||||
}
|
||||
|
||||
export interface LendingStats {
|
||||
totalSuppliedUsd: number;
|
||||
totalBorrowedUsd: number;
|
||||
totalCollateralUsd: number;
|
||||
utilizationRate: number;
|
||||
avgSupplyAPY: number;
|
||||
avgBorrowAPY: number;
|
||||
totalUsers: number;
|
||||
activeBorrowers: number;
|
||||
totalTVL: number;
|
||||
}
|
||||
|
||||
export interface LendingMarket {
|
||||
id: number;
|
||||
market_name: string;
|
||||
contract_address: string;
|
||||
chain_id: number;
|
||||
collateral_asset: string;
|
||||
borrow_collateral_factor: number;
|
||||
liquidate_collateral_factor: number;
|
||||
liquidation_penalty: number;
|
||||
supply_cap: number;
|
||||
base_supply_apy: number;
|
||||
base_borrow_apy: number;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
export interface LendingAPYPoint {
|
||||
time: string;
|
||||
supply_apy: number;
|
||||
borrow_apy: number;
|
||||
}
|
||||
|
||||
export interface LendingAPYHistory {
|
||||
history: LendingAPYPoint[];
|
||||
current_supply_apy: number;
|
||||
current_borrow_apy: number;
|
||||
apy_change: number;
|
||||
period: string;
|
||||
}
|
||||
|
||||
export async function fetchLendingAPYHistory(
|
||||
period: '1W' | '1M' | '1Y' = '1W',
|
||||
chainId?: number
|
||||
): Promise<LendingAPYHistory | null> {
|
||||
try {
|
||||
const params = new URLSearchParams({ period });
|
||||
if (chainId) params.append('chain_id', chainId.toString());
|
||||
const response = await fetch(`${API_BASE_URL}/lending/apy-history?${params}`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
const data = await response.json();
|
||||
if (!data.success) return null;
|
||||
return data.data;
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching lending APY history:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchLendingPosition(address: string): Promise<LendingPosition | null> {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/lending/position/${address}`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
const data = await response.json();
|
||||
if (!data.success) return null;
|
||||
return data.data;
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching lending position:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchLendingStats(): Promise<LendingStats | null> {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/lending/stats`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
const data = await response.json();
|
||||
if (!data.success) return null;
|
||||
return data.data;
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching lending stats:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchLendingMarkets(): Promise<LendingMarket[]> {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/lending/markets`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!response.ok) return [];
|
||||
const data = await response.json();
|
||||
if (!data.success) return [];
|
||||
return data.data;
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching lending markets:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function notifySupplyCollateral(asset: string, amount: string, txHash?: string): Promise<void> {
|
||||
try {
|
||||
await fetch(`${API_BASE_URL}/lending/supply-collateral`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ asset, amount, tx_hash: txHash }),
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Error notifying supply collateral:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function notifyWithdrawCollateral(asset: string, amount: string, txHash?: string): Promise<void> {
|
||||
try {
|
||||
await fetch(`${API_BASE_URL}/lending/withdraw-collateral`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ asset, amount, tx_hash: txHash }),
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Error notifying withdraw collateral:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function notifyLendingBorrow(amount: string, txHash?: string): Promise<void> {
|
||||
try {
|
||||
await fetch(`${API_BASE_URL}/lending/borrow`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ amount, tx_hash: txHash }),
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Error notifying lending borrow:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function notifyLendingRepay(amount: string, txHash?: string): Promise<void> {
|
||||
try {
|
||||
await fetch(`${API_BASE_URL}/lending/repay`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ amount, tx_hash: txHash }),
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Error notifying lending repay:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function notifyLendingSupply(amount: string, txHash?: string): Promise<void> {
|
||||
try {
|
||||
await fetch(`${API_BASE_URL}/lending/supply`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ amount, tx_hash: txHash }),
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Error notifying lending supply:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function notifyLendingWithdraw(amount: string, txHash?: string): Promise<void> {
|
||||
try {
|
||||
await fetch(`${API_BASE_URL}/lending/withdraw`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ amount, tx_hash: txHash }),
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Error notifying lending withdraw:', error);
|
||||
}
|
||||
}
|
||||
360
webapp/lib/api/points.ts
Normal file
360
webapp/lib/api/points.ts
Normal file
@@ -0,0 +1,360 @@
|
||||
// Points API Client
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080/api';
|
||||
|
||||
// =====================
|
||||
// Type Definitions
|
||||
// =====================
|
||||
|
||||
export interface WalletRegisterData {
|
||||
walletAddress: string;
|
||||
inviteCode: string;
|
||||
usedCount: number;
|
||||
memberTier: string;
|
||||
vipLevel: number;
|
||||
totalPoints: number;
|
||||
globalRank: number;
|
||||
}
|
||||
|
||||
export interface DashboardData {
|
||||
totalPoints: number;
|
||||
globalRank: number;
|
||||
topPercentage: string;
|
||||
memberTier: string;
|
||||
vipLevel: number;
|
||||
pointsToNextTier: number;
|
||||
nextTier: string;
|
||||
season: {
|
||||
seasonNumber: number;
|
||||
seasonName: string;
|
||||
isLive: boolean;
|
||||
endTime: string;
|
||||
daysRemaining: number;
|
||||
hoursRemaining: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LeaderboardUser {
|
||||
rank: number;
|
||||
address: string;
|
||||
points: number;
|
||||
}
|
||||
|
||||
export interface LeaderboardData {
|
||||
topUsers: LeaderboardUser[];
|
||||
myRank: number;
|
||||
myPoints: number;
|
||||
}
|
||||
|
||||
export interface InviteCodeData {
|
||||
code: string;
|
||||
usedCount: number;
|
||||
maxUses: number;
|
||||
}
|
||||
|
||||
export interface RoleCount {
|
||||
icon: string;
|
||||
label: string;
|
||||
current: number;
|
||||
target: number;
|
||||
}
|
||||
|
||||
export interface TeamTVLData {
|
||||
currentTVL: string;
|
||||
targetTVL: string;
|
||||
progressPercent: number;
|
||||
totalMembers: number;
|
||||
roles: RoleCount[];
|
||||
}
|
||||
|
||||
export interface ActivityRecord {
|
||||
type: string;
|
||||
userAddress: string;
|
||||
friendAddress?: string;
|
||||
inviteCode?: string;
|
||||
points: number;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface ActivitiesData {
|
||||
activities: ActivityRecord[];
|
||||
pagination: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
totalPage: number;
|
||||
};
|
||||
}
|
||||
|
||||
// =====================
|
||||
// API Functions
|
||||
// =====================
|
||||
|
||||
/**
|
||||
* Register or retrieve a user by wallet address
|
||||
* @param walletAddress - The connected wallet address
|
||||
*/
|
||||
export async function registerWallet(walletAddress: string): Promise<WalletRegisterData | null> {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/points/wallet-register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ wallet_address: walletAddress }),
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Wallet register API error:', response.status);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!data.success) {
|
||||
console.error('Wallet register error:', data.error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.data;
|
||||
} catch (error: unknown) {
|
||||
console.error('Error registering wallet:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch user points dashboard
|
||||
*/
|
||||
export async function fetchDashboard(walletAddress?: string): Promise<DashboardData | null> {
|
||||
try {
|
||||
const params = walletAddress ? `?wallet_address=${encodeURIComponent(walletAddress)}` : '';
|
||||
const response = await fetch(`${API_BASE_URL}/points/dashboard${params}`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Dashboard API error:', response.status);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
console.error('Dashboard API error:', data.error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.data;
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching dashboard:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch leaderboard
|
||||
* @param limit - Number of top users to fetch (default: 5)
|
||||
* @param walletAddress - Optional wallet address to identify current user's rank
|
||||
*/
|
||||
export async function fetchLeaderboard(limit: number = 5, walletAddress?: string): Promise<LeaderboardData | null> {
|
||||
try {
|
||||
const params = new URLSearchParams({ limit: limit.toString() });
|
||||
if (walletAddress) params.set('wallet_address', walletAddress);
|
||||
const response = await fetch(`${API_BASE_URL}/points/leaderboard?${params}`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Leaderboard API error:', response.status);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
console.error('Leaderboard API error:', data.error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.data;
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching leaderboard:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch user's invite code information
|
||||
*/
|
||||
export async function fetchInviteCode(): Promise<InviteCodeData | null> {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/points/invite-code`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch invite code');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to fetch invite code');
|
||||
}
|
||||
|
||||
return data.data;
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching invite code:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind an invite code
|
||||
* @param code - Invite code to bind
|
||||
* @param signature - User signature for verification
|
||||
* @param walletAddress - Optional wallet address of the current user
|
||||
*/
|
||||
export async function bindInviteCode(code: string, signature: string, walletAddress?: string): Promise<{ success: boolean; message?: string; error?: string }> {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/points/bind-invite`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ code, signature, wallet_address: walletAddress }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok || !data.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: data.error || 'Failed to bind invite code',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: data.message,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
console.error('Error binding invite code:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch team TVL statistics
|
||||
* @param walletAddress - Optional wallet address to identify current user's team
|
||||
*/
|
||||
export async function fetchTeamTVL(walletAddress?: string): Promise<TeamTVLData | null> {
|
||||
try {
|
||||
const params = walletAddress ? `?wallet_address=${encodeURIComponent(walletAddress)}` : '';
|
||||
const response = await fetch(`${API_BASE_URL}/points/team${params}`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Team TVL API error:', response.status, response.statusText);
|
||||
|
||||
// 返回默认值而不是 null
|
||||
return {
|
||||
currentTVL: "$0",
|
||||
targetTVL: "$10M",
|
||||
progressPercent: 0,
|
||||
totalMembers: 0,
|
||||
roles: []
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
console.error('Team TVL API error:', data.error);
|
||||
// 返回默认值
|
||||
return {
|
||||
currentTVL: "$0",
|
||||
targetTVL: "$10M",
|
||||
progressPercent: 0,
|
||||
totalMembers: 0,
|
||||
roles: []
|
||||
};
|
||||
}
|
||||
|
||||
return data.data;
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching team TVL:', error);
|
||||
// 返回默认值而不是 null
|
||||
return {
|
||||
currentTVL: "$0",
|
||||
targetTVL: "$10M",
|
||||
progressPercent: 0,
|
||||
totalMembers: 0,
|
||||
roles: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch user activities
|
||||
* @param type - Activity type: 'all', 'referrals', 'deposits'
|
||||
* @param page - Page number (default: 1)
|
||||
* @param pageSize - Items per page (default: 10)
|
||||
* @param walletAddress - Optional wallet address
|
||||
*/
|
||||
export async function fetchActivities(
|
||||
type: 'all' | 'referrals' | 'deposits' = 'all',
|
||||
page: number = 1,
|
||||
pageSize: number = 10,
|
||||
walletAddress?: string
|
||||
): Promise<ActivitiesData | null> {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
type,
|
||||
page: page.toString(),
|
||||
pageSize: pageSize.toString(),
|
||||
});
|
||||
if (walletAddress) params.set('wallet_address', walletAddress);
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/points/activities?${params}`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch activities');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to fetch activities');
|
||||
}
|
||||
|
||||
return data.data;
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching activities:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format points to display format (e.g., 52000 -> "52k")
|
||||
*/
|
||||
export function formatPoints(points: number): string {
|
||||
if (points >= 1000000) {
|
||||
return `${(points / 1000000).toFixed(1)}M`;
|
||||
} else if (points >= 1000) {
|
||||
return `${(points / 1000).toFixed(0)}k`;
|
||||
}
|
||||
return points.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate progress percentage
|
||||
*/
|
||||
export function calculateProgress(current: number, target: number): number {
|
||||
if (target === 0) return 0;
|
||||
return Math.round((current / target) * 100 * 100) / 100;
|
||||
}
|
||||
12
webapp/lib/api/tokens.ts
Normal file
12
webapp/lib/api/tokens.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// Token interface for swap
|
||||
export interface Token {
|
||||
symbol: string;
|
||||
name: string;
|
||||
decimals: number; // 配置值(兜底)
|
||||
onChainDecimals?: number; // 链上实际读取值,优先使用
|
||||
iconUrl: string;
|
||||
contractAddress: string;
|
||||
chainId: number;
|
||||
tokenType: 'stablecoin' | 'yield-token';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user