跳至主要内容

外汇 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 之前调用 setForexAccessTokensetForexEngineUrl。未设置 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;

参数

参数类型必需描述
poolIdstring要获取指标的资金池 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>
);
}

另请参阅