This document covers the API endpoints for the FTUSD dashboard, including user balances, staking info, and reward previews.
| Endpoint | Method | Description |
|---|---|---|
/ftusd/user/balances |
GET | Get user's ftUSD balances and USD values |
/ftusd/preview/claim |
GET | Preview claimable FT rewards with USD value |
/ftusd/preview/mint |
GET | Preview ftUSD output for collateral input |
/ftusd/preview/redeem |
GET | Preview collateral output for ftUSD redemption |
/ftusd/preview/stake |
GET | Preview sftUSD shares for ftUSD deposit |
/ftusd/preview/unstake |
GET | Preview ftUSD output for sftUSD redemption |
All preview endpoints (except /preview/claim) now return a consistent response format with both raw and formatted values:
{
"success": true,
"chain_id": 146,
"output": "1000000000",
"output_formatted": "1000",
"output_decimals": 6,
"input": "1000000000",
"input_formatted": "1000",
"input_decimals": 6
}| Field | Type | Description |
|---|---|---|
output |
string | Raw output amount in wei |
output_formatted |
string | Human-readable output (e.g., "1000.50") |
output_decimals |
int | Decimals for output token (6 for ftUSD/sftUSD) |
input |
string | Raw input amount in wei |
input_formatted |
string | Human-readable input (e.g., "1000.00") |
input_decimals |
int | Decimals for input token |
Get a user's ftUSD-related balances including USD values calculated using on-chain oracle prices.
GET /ftusd/user/balances?address={wallet_address}&chainId={chain_id}
| Parameter | Required | Description |
|---|---|---|
address |
Yes | User's wallet address (0x...) |
chainId |
No | Filter by chain ID. If omitted, returns data for all chains |
{
"address": "0x1234567890abcdef1234567890abcdef12345678",
"chain_id": 1,
"chains": [
{
"chain_id": 1,
"ftusd_balance": "1000000000",
"ftusd_decimals": 6,
"sftusd_balance": "500000000",
"sftusd_decimals": 6,
"ft_earnings": "25000000000000000000",
"ft_earnings_decimals": 18,
"ft_earnings_usd": "12.5",
"investable_collaterals": [
{
"token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"token_symbol": "USDC",
"balance": "5000000000",
"decimals": 6,
"value_usd": "5000"
},
{
"token_address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"token_symbol": "USDT",
"balance": "2500000000",
"decimals": 6,
"value_usd": "2499.75"
}
]
}
],
"totals": {
"total_ftusd_balance": "1000000000",
"total_ftusd_decimals": 6,
"total_sftusd_balance": "500000000",
"total_sftusd_decimals": 6,
"total_ft_earnings": "25000000000000000000",
"total_ft_earnings_decimals": 18,
"total_ft_earnings_usd": "12.5",
"total_investable_usd": "7499.75"
}
}| Field | Type | Description |
|---|---|---|
ftusd_balance |
string | Raw ftUSD balance (divide by 10^ftusd_decimals for display) |
ftusd_decimals |
int | Decimals for ftUSD (always 6) |
sftusd_balance |
string | Raw sftUSD (staked) balance |
sftusd_decimals |
int | Decimals for sftUSD (always 6) |
ft_earnings |
string | Raw accrued FT rewards from staking |
ft_earnings_decimals |
int | Decimals for FT token (always 18) |
ft_earnings_usd |
string | USD value of FT earnings (decimal string, e.g., "12.5") |
investable_collaterals |
array | Tokens user can use to mint ftUSD |
value_usd |
string | USD value of collateral (decimal string) |
total_investable_usd |
string | Sum of all investable collateral USD values |
All USD values are returned as decimal strings without currency symbols:
"12.5"means $12.50"1234.567890"means $1,234.567890"0.000001"means $0.000001"0"means $0.00
USD prices are fetched from the Aave oracle on-chain.
const response = await fetch(
`https://api.flyingtulip.com/ftusd/user/balances?address=${walletAddress}`
);
const data = await response.json();
// Display ftUSD balance
const ftUsdBalance = Number(data.totals.total_ftusd_balance) / 1e6;
console.log(`ftUSD Balance: ${ftUsdBalance.toFixed(2)}`);
// Display FT earnings with USD value
const ftEarnings = Number(data.totals.total_ft_earnings) / 1e18;
const ftEarningsUsd = parseFloat(data.totals.total_ft_earnings_usd);
console.log(`FT Earnings: ${ftEarnings.toFixed(4)} FT ($${ftEarningsUsd.toFixed(2)})`);
// Display total investable USD
const investableUsd = parseFloat(data.totals.total_investable_usd);
console.log(`Investable: $${investableUsd.toFixed(2)}`);Preview the amount of FT tokens a user can claim from their sftUSD staking, including USD value.
GET /ftusd/preview/claim?address={wallet_address}&chainId={chain_id}
| Parameter | Required | Description |
|---|---|---|
address |
Yes | User's wallet address (0x...) |
chainId |
No | Chain ID (default: 1) |
{
"success": true,
"chain_id": 146,
"address": "0x540bCa1D332EA9B8cf46473d97AE73DDD3Cc5fE9",
"claimable_raw": "4237797833730902631295",
"claimable_ft": "4237.797833730902631295",
"ft_price": "0.1",
"claimable_usd": "423.77978337"
}| Field | Type | Description |
|---|---|---|
address |
string | The wallet address queried |
claimable_raw |
string | Raw claimable FT amount in wei (18 decimals) |
claimable_ft |
string | Formatted FT amount (e.g., "4237.79") |
ft_price |
string | FT price in USD (e.g., "0.1" = $0.10 per FT) |
claimable_usd |
string | USD value of claimable FT (e.g., "423.78") |
const response = await fetch(
`https://api.flyingtulip.com/ftusd/preview/claim?address=${walletAddress}&chainId=146`
);
const data = await response.json();
if (data.success) {
// Use pre-formatted values directly
const claimableFT = parseFloat(data.claimable_ft);
const ftPrice = parseFloat(data.ft_price);
const claimableUSD = parseFloat(data.claimable_usd);
console.log(`Claimable: ${claimableFT.toFixed(2)} FT`);
console.log(`FT Price: $${ftPrice.toFixed(2)}`);
console.log(`USD Value: $${claimableUSD.toFixed(2)}`);
}Preview the ftUSD output for a given collateral input.
GET /ftusd/preview/mint?collateralToken={token_address}&amount={amount}&chainId={chain_id}
| Parameter | Required | Description |
|---|---|---|
collateralToken |
Yes | Collateral token address (e.g., USDC) |
amount |
Yes | Collateral amount in wei |
chainId |
No | Chain ID (default: 1) |
{
"success": true,
"chain_id": 146,
"output": "999500000",
"output_formatted": "999.5",
"output_decimals": 6,
"input": "1000000000",
"input_formatted": "1000",
"input_decimals": 6
}// Preview minting with 1000 USDC
const usdcAddress = "0x29219dd400f2Bf60E5a23d13Be72B486D4038894"; // Sonic USDC
const amount = (1000 * 1e6).toString(); // 1000 USDC in wei
const response = await fetch(
`https://api.flyingtulip.com/ftusd/preview/mint?collateralToken=${usdcAddress}&amount=${amount}&chainId=146`
);
const data = await response.json();
// Use the formatted output directly
console.log(`You will receive: ${data.output_formatted} ftUSD`);
// Or calculate from raw values
const ftUsdOutput = Number(data.output) / Math.pow(10, data.output_decimals);
console.log(`You will receive: ${ftUsdOutput.toFixed(2)} ftUSD`);Preview the collateral output for redeeming ftUSD.
GET /ftusd/preview/redeem?collateralToken={token_address}&amount={amount}&chainId={chain_id}
| Parameter | Required | Description |
|---|---|---|
collateralToken |
Yes | Desired collateral token address |
amount |
Yes | ftUSD amount to redeem (in wei, 6 decimals) |
chainId |
No | Chain ID (default: 1) |
{
"success": true,
"chain_id": 146,
"output": "999000000",
"output_formatted": "999",
"output_decimals": 6,
"input": "1000000000",
"input_formatted": "1000",
"input_decimals": 6
}Preview the sftUSD shares for staking ftUSD.
GET /ftusd/preview/stake?amount={amount}&chainId={chain_id}
| Parameter | Required | Description |
|---|---|---|
amount |
Yes | ftUSD amount to stake (in wei, 6 decimals) |
chainId |
No | Chain ID (default: 1) |
{
"success": true,
"chain_id": 146,
"output": "1000000000",
"output_formatted": "1000",
"output_decimals": 6,
"input": "1000000000",
"input_formatted": "1000",
"input_decimals": 6
}The output is the sftUSD shares you'll receive. Initially 1:1 with ftUSD.
Preview the ftUSD output for unstaking sftUSD shares.
GET /ftusd/preview/unstake?shares={shares}&chainId={chain_id}
| Parameter | Required | Description |
|---|---|---|
shares |
Yes | sftUSD shares to unstake (in wei, 6 decimals) |
chainId |
No | Chain ID (default: 1) |
{
"success": true,
"chain_id": 146,
"output": "1005000000",
"output_formatted": "1005",
"output_decimals": 6,
"input": "1000000000",
"input_formatted": "1000",
"input_decimals": 6
}Here's how to map API responses to common dashboard components:
| Display | API Field | Calculation |
|---|---|---|
| ftUSD Balance | totals.total_ftusd_balance |
value / 10^6 |
| ftUSD Staked | totals.total_sftusd_balance |
value / 10^6 |
| Unclaimed FT | Use /ftusd/preview/claim |
claimable_ft field |
| Unclaimed FT (USD) | Use /ftusd/preview/claim |
claimable_usd field |
| FT Price | Use /ftusd/preview/claim |
ft_price field |
| Investable Balance | totals.total_investable_usd |
parseFloat directly |
interface DashboardStats {
ftUsdBalance: number;
ftUsdStaked: number;
claimableFT: number;
claimableFTUsd: number;
ftPrice: number;
investableUsd: number;
}
async function fetchDashboardStats(address: string, chainId: number): Promise<DashboardStats> {
// Fetch user balances
const balancesRes = await fetch(`/ftusd/user/balances?address=${address}&chainId=${chainId}`);
const balances = await balancesRes.json();
// Fetch claimable rewards with USD value
const claimRes = await fetch(`/ftusd/preview/claim?address=${address}&chainId=${chainId}`);
const claim = await claimRes.json();
return {
ftUsdBalance: Number(balances.totals?.total_ftusd_balance || 0) / 1e6,
ftUsdStaked: Number(balances.totals?.total_sftusd_balance || 0) / 1e6,
claimableFT: parseFloat(claim.claimable_ft || "0"),
claimableFTUsd: parseFloat(claim.claimable_usd || "0"),
ftPrice: parseFloat(claim.ft_price || "0"),
investableUsd: parseFloat(balances.totals?.total_investable_usd || "0"),
};
}
// Display
function StatsCard({ stats }: { stats: DashboardStats }) {
return (
<div>
<div>ftUSD Balance: {stats.ftUsdBalance.toFixed(2)}</div>
<div>Staked: {stats.ftUsdStaked.toFixed(2)} sftUSD</div>
<div>
Claimable: {stats.claimableFT.toFixed(2)} FT
(${stats.claimableFTUsd.toFixed(2)})
</div>
<div>FT Price: ${stats.ftPrice.toFixed(2)}</div>
<div>Investable: ${stats.investableUsd.toFixed(2)}</div>
</div>
);
}// Preview component using new formatted fields
async function previewStake(amount: string, chainId: number) {
const response = await fetch(
`/ftusd/preview/stake?amount=${amount}&chainId=${chainId}`
);
const data = await response.json();
if (data.success) {
// Use formatted values directly - no manual division needed!
return {
inputDisplay: `${data.input_formatted} ftUSD`,
outputDisplay: `${data.output_formatted} sftUSD`,
};
}
}
// Example for mint preview
async function previewMint(collateralToken: string, amount: string, chainId: number) {
const response = await fetch(
`/ftusd/preview/mint?collateralToken=${collateralToken}&amount=${amount}&chainId=${chainId}`
);
const data = await response.json();
if (data.success) {
return {
inputDisplay: `${data.input_formatted} tokens`,
outputDisplay: `${data.output_formatted} ftUSD`,
// decimals available if needed for further calculations
inputDecimals: data.input_decimals,
outputDecimals: data.output_decimals,
};
}
}All endpoints return HTTP error codes with plain text messages:
| Code | Description |
|---|---|
| 400 | Bad Request - Invalid parameters |
| 500 | Internal Server Error |
| 503 | Service Unavailable - Rate limited |
Example error response:
HTTP/1.1 400 Bad Request
Content-Type: text/plain
Invalid or missing address
- USD values use on-chain oracle prices from FlyingTulip Oracle (for FT) and Aave Oracle (for collaterals)
- All raw balances are strings to avoid JavaScript number precision issues with large values
- USD values are decimal strings - parse with
parseFloat()for display - Chain ID defaults to 1 (Ethereum mainnet) if not specified. Use
chainId=146for Sonic. - Rate limiting is in place - handle 503 errors with exponential backoff
- FT claims are in FT tokens (18 decimals), NOT ftUSD - the API provides formatted values for convenience
- All preview endpoints now include formatted values -
output_formatted,input_formatted, and decimals fields