外汇 Hooks
ONE SDK 导出 5 个专用 hooks 用于链上外汇(StableFX)交易,外加 token 管理函数 用于认证。这些 hooks 是 独立的——不需要 OneProvider,并自行管理认证 token 和 engine URL。
导入
import {
// Hooks
useForexPools,
useForexInvestments,
useForexSimulation,
useForexPoolData,
useForexTrading,
// Token management
setForexAccessToken,
clearForexAccessToken,
setForexEngineUrl,
} from '@one_deploy/sdk';
Token 管理
需要独立认证
外汇 hooks 不会 从 OneProvider 上下文读取数据。你必须在使用任何外汇 hook 之前调用 setForexAccessToken 和 setForexEngineUrl。未设置 token 就调用 hook 会产生 UNAUTHORIZED 错误。
设置
import {
setForexAccessToken,
clearForexAccessToken,
setForexEngineUrl,
} from '@one_deploy/sdk';
// 1. Set the engine URL (call once at app startup)
setForexEngineUrl('https://engine.one23.io');
// 2. Set the user's access token after authentication
function onUserLogin(accessToken: string) {
setForexAccessToken(accessToken);
}
// 3. Clear on logout
function onUserLogout() {
clearForexAccessToken();
}
函数签名
/** Set the bearer token used by all forex hooks and API calls.
* Call this once after authentication. The token is stored in module-level
* state and shared across all forex hook instances. */
function setForexAccessToken(token: string): void;
/** Clear the current forex access token.
* Subsequent hook calls will return UNAUTHORIZED errors until a new token is set. */
function clearForexAccessToken(): void;
/** Set the ONE Engine URL for forex API calls.
* Defaults to 'https://engine.one23.io' if not called.
* Useful for staging/development environments. */
function setForexEngineUrl(url: string): void;
为什么采用独立模式?
外汇组件主要为 React Native 设计,在该环境中 Web 小组件使用的 provider 模式不适用。独立 token 函数允许你将外汇集成到任何 React Native 导航结构中,无需嵌套 provider。它们在 Web 上同样可用。
完整设置示例
import {
setForexAccessToken,
clearForexAccessToken,
setForexEngineUrl,
useForexPools,
} from '@one_deploy/sdk';
import { useEffect, useState } from 'react';
function ForexApp() {
const [isReady, setIsReady] = useState(false);
useEffect(() => {
async function init() {
setForexEngineUrl('https://engine.one23.io');
const token = await fetchAccessTokenFromYourAuthService();
setForexAccessToken(token);
setIsReady(true);
}
init();
return () => {
clearForexAccessToken();
};
}, []);
if (!isReady) return <p>Authenticating...</p>;
return (
<>
<PoolList />
<InvestmentDashboard />
</>
);
}
核心类型
以下类型在所有外汇 hooks 之间共享:
type ForexPoolType = 'clearing' | 'hedging' | 'insurance';
interface ForexPool {
id: string;
type: ForexPoolType;
totalValueLocked: number;
apy: number;
utilization: number;
currency: string;
createdAt: string;
updatedAt: string;
}
interface ForexInvestment {
id: string;
poolId: string;
poolType: ForexPoolType;
amount: number;
currentValue: number;
pnl: number;
pnlPercentage: number;
currency: string;
status: 'active' | 'matured' | 'withdrawn';
investedAt: string;
maturesAt: string;
}
interface ForexPoolDailyMetric {
date: string;
tvl: number;
apy: number;
utilization: number;
volume: number;
feeRevenue: number;
}
useForexPools
获取所有外汇流动性池(清算池、对冲池和保险池)。
签名
function useForexPools(): UseForexPoolsResult;
返回类型
interface UseForexPoolsResult {
/** Array of all forex pools. */
pools: ForexPool[];
/** Whether the initial fetch is in progress. */
isLoading: boolean;
/** Error object, or null. */
error: OneSDKError | null;
/** Manually trigger a refetch. */
refetch: () => Promise<void>;
}
用法
import { useForexPools } from '@one_deploy/sdk';
function PoolList() {
const { pools, isLoading, error } = useForexPools();
if (isLoading) return <p>Loading pools...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h2>Forex Liquidity Pools</h2>
{pools.map((pool) => (
<div key={pool.id} style={{ padding: 12, borderBottom: '1px solid #eee' }}>
<strong>{pool.type.toUpperCase()} Pool</strong>
<dl>
<dt>TVL</dt><dd>${pool.totalValueLocked.toLocaleString()}</dd>
<dt>APY</dt><dd>{pool.apy.toFixed(2)}%</dd>
<dt>Utilization</dt><dd>{(pool.utilization * 100).toFixed(1)}%</dd>
<dt>Currency</dt><dd>{pool.currency}</dd>
</dl>
</div>
))}
</div>
);
}
useForexInvestments
获取已认证用户在所有资金池中的外汇投资头寸。
签名
function useForexInvestments(): UseForexInvestmentsResult;
返回类型
interface UseForexInvestmentsResult {
/** Array of the user's forex investments. */
investments: ForexInvestment[];
/** Total invested amount across all positions (in USD). */
totalInvested: number;
/** Total current value across all positions (in USD). */
totalCurrentValue: number;
/** Aggregate P&L in USD. */
totalPnl: number;
/** Whether the fetch is in progress. */
isLoading: boolean;
/** Error object, or null. */
error: OneSDKError | null;
/** Manually trigger a refetch. */
refetch: () => Promise<void>;
}
用法
import { useForexInvestments } from '@one_deploy/sdk';
function InvestmentDashboard() {
const {
investments,
totalInvested,
totalCurrentValue,
totalPnl,
isLoading,
error,
} = useForexInvestments();
if (isLoading) return <p>Loading investments...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h2>My Forex Investments</h2>
<div style={{ display: 'flex', gap: 24 }}>
<div>
<strong>Total Invested</strong>
<p>${totalInvested.toLocaleString()}</p>
</div>
<div>
<strong>Current Value</strong>
<p>${totalCurrentValue.toLocaleString()}</p>
</div>
<div>
<strong>P&L</strong>
<p style={{ color: totalPnl >= 0 ? 'green' : 'red' }}>
{totalPnl >= 0 ? '+' : ''}${totalPnl.toFixed(2)}
</p>
</div>
</div>
<h3>Positions</h3>
{investments.map((inv) => (
<div key={inv.id} style={{ padding: 8, borderBottom: '1px solid #eee' }}>
<strong>{inv.poolType} pool</strong> |
Invested: ${inv.amount.toFixed(2)} |
Current: ${inv.currentValue.toFixed(2)} |
P&L: <span style={{ color: inv.pnl >= 0 ? 'green' : 'red' }}>
{inv.pnl >= 0 ? '+' : ''}{inv.pnlPercentage.toFixed(2)}%
</span> |
Status: {inv.status}
</div>
))}
</div>
);
}
useForexSimulation
使用 forexSimulationEngine 在客户端运行资金池模拟。可在投资前用于建模资金分配方案。
签名
function useForexSimulation(): UseForexSimulationResult;
返回类型
interface UseForexSimulationResult {
/** Run a simulation with given parameters. */
simulate: (params: ForexSimulationParams) => Promise<ForexSimulationOutput>;
/** The most recent simulation result, or null. */
result: ForexSimulationOutput | null;
/** Whether a simulation is currently running. */
isSimulating: boolean;
/** Error object, or null. */
error: OneSDKError | null;
/** Clear the current result. */
reset: () => void;
}
interface ForexSimulationParams {
/** Total deposit amount in USD. */
depositAmount: number;
/** Duration of the simulation in days. */
durationDays: number;
/** Target pool type. */
poolType: ForexPoolType;
/** Optional: override the capital split ratio (defaults to FOREX_CAPITAL_SPLIT = 0.5). */
capitalSplitOverride?: number;
}
interface ForexSimulationOutput {
/** Projected value at the end of the simulation period. */
projectedValue: number;
/** Projected yield (profit) in USD. */
projectedYield: number;
/** Projected APY based on the simulation. */
projectedApy: number;
/** Breakdown of capital allocation across pools. */
allocation: {
tradingCapital: number;
clearingPool: number;
hedgingPool: number;
insurancePool: number;
};
/** Day-by-day projection data points. */
projectionCurve: Array<{
day: number;
value: number;
yield: number;
}>;
}
用法
import { useForexSimulation } from '@one_deploy/sdk';
function SimulationTool() {
const { simulate, result, isSimulating, error, reset } = useForexSimulation();
const handleSimulate = async () => {
await simulate({
depositAmount: 10000,
durationDays: 90,
poolType: 'clearing',
});
};
return (
<div>
<h2>Pool Simulation</h2>
<button onClick={handleSimulate} disabled={isSimulating}>
{isSimulating ? 'Simulating...' : 'Simulate $10,000 / 90 days / Clearing'}
</button>
{result && (
<button onClick={reset}>Clear</button>
)}
{error && <p>Error: {error.message}</p>}
{result && (
<div>
<dl>
<dt>Projected Value</dt><dd>${result.projectedValue.toFixed(2)}</dd>
<dt>Projected Yield</dt><dd>${result.projectedYield.toFixed(2)}</dd>
<dt>Projected APY</dt><dd>{result.projectedApy.toFixed(2)}%</dd>
</dl>
<h3>Capital Allocation</h3>
<ul>
<li>Trading Capital: ${result.allocation.tradingCapital.toFixed(2)}</li>
<li>Clearing Pool: ${result.allocation.clearingPool.toFixed(2)}</li>
<li>Hedging Pool: ${result.allocation.hedgingPool.toFixed(2)}</li>
<li>Insurance Pool: ${result.allocation.insurancePool.toFixed(2)}</li>
</ul>
<h3>Projection Curve</h3>
<ul>
{result.projectionCurve
.filter((_, i) => i % 10 === 0 || i === result.projectionCurve.length - 1)
.map((point) => (
<li key={point.day}>
Day {point.day}: ${point.value.toFixed(2)} (+${point.yield.toFixed(2)})
</li>
))}
</ul>
</div>
)}
</div>
);
}
useForexPoolData
获取特定外汇资金池的每日详细指标。可用于资金池分析和图表渲染。
签名
function useForexPoolData(poolId?: string): UseForexPoolDataResult;
参数
| 参数 | 类型 | 必需 | 描述 |
|---|---|---|---|
poolId | string | 否 | 要获取指标的资金池 ID。当为 undefined 时,hook 处于空闲状态。 |
返回类型
interface UseForexPoolDataResult {
/** The pool object, or null if not loaded. */
pool: ForexPool | null;
/** Array of daily metric snapshots. */
dailyMetrics: ForexPoolDailyMetric[];
/** Whether the fetch is in progress. */
isLoading: boolean;
/** Error object, or null. */
error: OneSDKError | null;
/** Manually trigger a refetch. */
refetch: () => Promise<void>;
}
用法
import { useForexPoolData } from '@one_deploy/sdk';
function PoolDetailView({ poolId }: { poolId: string }) {
const { pool, dailyMetrics, isLoading, error } = useForexPoolData(poolId);
if (isLoading) return <p>Loading pool data...</p>;
if (error) return <p>Error: {error.message}</p>;
if (!pool) return <p>Pool not found.</p>;
return (
<div>
<h2>{pool.type.toUpperCase()} Pool</h2>
<dl>
<dt>TVL</dt><dd>${pool.totalValueLocked.toLocaleString()}</dd>
<dt>APY</dt><dd>{pool.apy.toFixed(2)}%</dd>
<dt>Utilization</dt><dd>{(pool.utilization * 100).toFixed(1)}%</dd>
</dl>
<h3>Daily Metrics (last {dailyMetrics.length} days)</h3>
<table>
<thead>
<tr>
<th>日期</th>
<th>TVL</th>
<th>APY</th>
<th>利用率</th>
<th>成交量</th>
<th>手续费</th>
</tr>
</thead>
<tbody>
{dailyMetrics.slice(-7).map((m) => (
<tr key={m.date}>
<td>{m.date}</td>
<td>${m.tvl.toLocaleString()}</td>
<td>{m.apy.toFixed(2)}%</td>
<td>{(m.utilization * 100).toFixed(1)}%</td>
<td>${m.volume.toLocaleString()}</td>
<td>${m.feeRevenue.toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
useForexTrading
一个 组合 hook,将上述四个 hooks 的功能聚合到一个接口中,并提供创建和管理外汇头寸的方法。
签名
function useForexTrading(): UseForexTradingResult;
返回类型
interface UseForexTradingResult {
/** All forex pools. */
pools: ForexPool[];
/** User's investment positions. */
investments: ForexInvestment[];
/** Create a new forex investment. */
createInvestment: (params: CreateForexInvestmentParams) => Promise<ForexInvestment>;
/** Withdraw from an existing investment. */
withdrawInvestment: (investmentId: string) => Promise<void>;
/** Run a pool simulation. */
simulate: (params: ForexSimulationParams) => Promise<ForexSimulationOutput>;
/** Fetch daily metrics for a pool. */
getPoolData: (poolId: string) => Promise<{
pool: ForexPool;
dailyMetrics: ForexPoolDailyMetric[];
}>;
/** Whether any data is loading. */
isLoading: boolean;
/** The most recent error, or null. */
error: OneSDKError | null;
/** Refetch all data (pools and investments). */
refetch: () => Promise<void>;
}
interface CreateForexInvestmentParams {
/** The pool ID to invest in. */
poolId: string;
/** Investment amount in USD. */
amount: number;
/** Investment duration in days. */
durationDays: number;
}
用法
import { useForexTrading } from '@one_deploy/sdk';
function ForexTradingDashboard() {
const {
pools,
investments,
createInvestment,
withdrawInvestment,
simulate,
isLoading,
error,
refetch,
} = useForexTrading();
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
const handleInvest = async (poolId: string) => {
// Simulate first
const sim = await simulate({
depositAmount: 5000,
durationDays: 30,
poolType: 'clearing',
});
console.log('Projected yield:', sim.projectedYield);
// Create the investment
const investment = await createInvestment({
poolId,
amount: 5000,
durationDays: 30,
});
console.log('Created investment:', investment.id);
await refetch();
};
return (
<div>
{/* Pool overview */}
<h2>Forex Pools</h2>
{pools.map((pool) => (
<div key={pool.id} style={{ padding: 12, borderBottom: '1px solid #eee' }}>
<strong>{pool.type.toUpperCase()}</strong> |
TVL: ${pool.totalValueLocked.toLocaleString()} |
APY: {pool.apy.toFixed(2)}%
<button onClick={() => handleInvest(pool.id)} style={{ marginLeft: 12 }}>
Invest $5,000
</button>
</div>
))}
{/* Active investments */}
<h2>My Investments ({investments.length})</h2>
{investments.map((inv) => (
<div key={inv.id} style={{ padding: 8, borderBottom: '1px solid #eee' }}>
{inv.poolType} pool |
${inv.amount.toFixed(2)} invested |
Current: ${inv.currentValue.toFixed(2)} |
Status: {inv.status}
{inv.status === 'matured' && (
<button onClick={() => withdrawInvestment(inv.id)} style={{ marginLeft: 8 }}>
Withdraw
</button>
)}
</div>
))}
</div>
);
}
完整集成示例
端到端示例展示 token 设置、资金池浏览、模拟和投资:
import {
setForexAccessToken,
setForexEngineUrl,
clearForexAccessToken,
useForexPools,
useForexSimulation,
useForexTrading,
FOREX_CAPITAL_SPLIT,
} from '@one_deploy/sdk';
import { useEffect, useState } from 'react';
function ForexScreen({ userAccessToken }: { userAccessToken: string }) {
const [ready, setReady] = useState(false);
useEffect(() => {
setForexEngineUrl('https://engine.one23.io');
setForexAccessToken(userAccessToken);
setReady(true);
return () => clearForexAccessToken();
}, [userAccessToken]);
if (!ready) return <p>Initializing forex...</p>;
return <ForexContent />;
}
function ForexContent() {
const { pools, isLoading: poolsLoading } = useForexPools();
const { simulate, result: simResult, isSimulating } = useForexSimulation();
const {
investments,
createInvestment,
withdrawInvestment,
isLoading: tradingLoading,
error,
} = useForexTrading();
const [selectedPool, setSelectedPool] = useState<string | null>(null);
if (poolsLoading || tradingLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
const handleSimulate = async () => {
if (!selectedPool) return;
const pool = pools.find((p) => p.id === selectedPool);
if (!pool) return;
await simulate({
depositAmount: 10000,
durationDays: 60,
poolType: pool.type,
});
};
const handleInvest = async () => {
if (!selectedPool) return;
const investment = await createInvestment({
poolId: selectedPool,
amount: 10000,
durationDays: 60,
});
console.log('Created investment:', investment.id);
};
return (
<div>
<p>Capital split ratio: {FOREX_CAPITAL_SPLIT * 100}% trading / {FOREX_CAPITAL_SPLIT * 100}% reserves</p>
{/* Pool picker */}
<h2>Select Pool</h2>
<select
value={selectedPool ?? ''}
onChange={(e) => setSelectedPool(e.target.value || null)}
>
<option value="">-- Choose a pool --</option>
{pools.map((p) => (
<option key={p.id} value={p.id}>
{p.type.toUpperCase()} -- TVL: ${p.totalValueLocked.toLocaleString()} -- APY: {p.apy.toFixed(2)}%
</option>
))}
</select>
<button onClick={handleSimulate} disabled={!selectedPool || isSimulating}>
{isSimulating ? 'Running...' : 'Simulate $10K / 60 days'}
</button>
{simResult && (
<div>
<h3>Simulation Result</h3>
<p>Projected Value: ${simResult.projectedValue.toFixed(2)}</p>
<p>Projected Yield: ${simResult.projectedYield.toFixed(2)}</p>
<p>Projected APY: {simResult.projectedApy.toFixed(2)}%</p>
<button onClick={handleInvest}>Invest $10,000</button>
</div>
)}
{/* Investments */}
<h2>My Investments</h2>
{investments.length === 0 && <p>No investments yet.</p>}
{investments.map((inv) => (
<div key={inv.id}>
{inv.poolType} | ${inv.amount} -> ${inv.currentValue.toFixed(2)} |
{inv.status === 'matured' && (
<button onClick={() => withdrawInvestment(inv.id)}>Withdraw</button>
)}
</div>
))}
</div>
);
}
另请参阅
- Hooks 概览 -- 所有 hook 类别。
- 外汇概览 -- StableFX 架构、三池系统、货币对。
- 资金分配 -- 50/50 分配模式。
- 创建投资 -- 分步投资指南。
- AI 交易 Hooks -- 类似的 AI 交易独立 hooks。