fusanex is a crypto-native foreign exchange (FX) library.
It lets you:
- Fetch crypto/fiat prices from spot markets (e.g. Upbit KRW, Binance USD-like).
- Compute cross rates via a base asset (e.g. USDT/KRW from BTC/KRW and BTC/USDT).
- Query legal FX rates (fiat↔fiat) via a pluggable HTTP endpoint.
- Combine crypto markets + fiat FX to resolve “any to any” routes where possible.
Internally, it follows a hexagonal architecture:
- Domain: assets, currencies, exchange rates, cross-rate logic.
- Application:
FusanexServiceimplementingFusanexPort. - Adapters:
- Outbound: crypto markets (Upbit KRW, Binance USD-like) and legal FX.
- Inbound:
createFusanexfactory as the main entry point.
npm install fusanex
# or
yarn add fusanex
# or
pnpm add fusanexfusanex is written in TypeScript and works well in both TypeScript and JavaScript projects.
import fusanex from "fusanex";
async function main() {
// Create a client with default configuration.
// - Crypto: Upbit (KRW spot) + Binance (USD-like stable, e.g. USDT)
// - Fiat FX: your configured legal FX provider (or default, if wired)
const fx = fusanex();
// 1) Smart "universal" rate:
// - Uses crypto markets + fiat FX + hybrid routing under the hood.
const usdtKrw = await fx.rate("USDT", "KRW");
console.log("USDT/KRW (universal) =", usdtKrw);
// 2) Crypto-only smart rate:
// - Tries direct crypto markets, then cross via baseAsset.
// - Does NOT use fiat FX at all.
const btcEth = await fx.cryptoRate("BTC", "ETH");
console.log("BTC/ETH (crypto-only) =", btcEth);
// 3) Direct crypto spot market only:
// - No cross, no fiat FX, no routing.
const btcKrwDirect = await fx.directRate("BTC", "KRW");
console.log("BTC/KRW (direct spot) =", btcKrwDirect);
// 4) Legal FX (fiat↔fiat) only:
const usdKrwFiat = await fx.fiatRate("USD", "KRW");
console.log("USD/KRW (fiat FX) =", usdKrwFiat);
}
main().catch(console.error);The main object you get from fusanex() implements FusanexPort:
interface FusanexPort {
/**
* Universal router (crypto + fiat).
*/
rate(
from: CurrencyCode | BaseAsset,
to: CurrencyCode | BaseAsset
): Promise<number | null>;
/**
* Crypto-only smart router.
* - Uses cryptoMarkets only.
* - Tries direct markets first, then cross via baseAsset.
* - Does NOT use fiat FX.
*/
cryptoRate(
from: CurrencyCode | BaseAsset,
to: CurrencyCode | BaseAsset
): Promise<number | null>;
/**
* Direct crypto rate from spot markets only.
* - No cross via baseAsset.
* - No fiat FX.
*/
directRate(
from: CurrencyCode | BaseAsset,
to: CurrencyCode | BaseAsset
): Promise<number | null>;
/**
* Legal FX (fiat↔fiat) via configured FX provider.
* - Uses fiatMarkets only.
*/
fiatRate(from: CurrencyCode, to: CurrencyCode): Promise<number | null>;
}Where:
type BaseAsset = "BTC" | "ETH" | "XRP" | "BNB" | "SOL";
type FiatCurrencyCode = "USD" | "KRW" | "JPY" | "EUR" | "CNY" | "VND";
type StableCurrencyCode = "USDT" | "USDC";
type CurrencyCode = FiatCurrencyCode | StableCurrencyCode;
interface ExchangeRate {
base: CurrencyCode | BaseAsset;
quote: CurrencyCode | BaseAsset;
price: number;
}You can customize the behavior via FusanexConfig, passed to fusanex(config).
import fusanex from "fusanex";
const fx = fusanex({
/**
* Base asset for crypto cross routing.
* Example: "BTC" or "ETH".
*/
baseAsset: "BTC",
/**
* Crypto providers configuration.
* Example: which provider to use for KRW and USD-like markets.
*/
providers: {
KRW: "Upbit",
USD: "Binance",
},
/**
* Optional override for exchange API endpoints.
*/
binanceApiUrl: "https://api.binance.com",
upbitApiUrl: "https://api.upbit.com",
/**
* Fiat FX configuration (optional).
* If omitted, a default fiat FX provider may be used by the factory,
* depending on your internal wiring.
*/
fiatFx: {
endpoint: "https://example.com/fiat-fx",
// Map raw JSON response → { [pair: string]: number }
transform: (raw: unknown) => {
// user-defined transform here
return {
"USD/KRW": 1350.12,
"EUR/USD": 1.07,
};
},
},
});Example: custom base asset
const fx = fusanex({
baseAsset: "ETH",
});
const usdtKrw = await fx.crossRate("USDT", "KRW"); // now via ETH instead of BTCBy default, crypto prices are fetched from:
- UpbitKRWMarketAdapter
- Supports pairs like
BTC/KRW,ETH/KRW,USDT/KRW… - Uses:
GET {upbitApiUrl}/ticker?markets=KRW-{ASSET}.
- Supports pairs like
- BinanceUSDLikeMarketAdapter
- Supports pairs like
BTC/USDT,ETH/USDT,XRP/USDT… - Uses:
GET {binanceApiUrl}/ticker/price?symbol={BASE}{STABLE}.
- Supports pairs like
directRate only looks at these direct spot markets:
const btcKrw = await fx.directRate("BTC", "KRW"); // Upbit
const ethUsdt = await fx.directRate("ETH", "USDT"); // Binance
if (!btcKrw) {
console.log("BTC/KRW not available");
}For cross rates, fusanex uses the base asset (default BTC) as a bridge.
Conceptually:
USDT/KRW = (BTC/KRW) / (BTC/USDT)Usage:
const cross = await fx.crossRate("USDT", "KRW");
console.log(cross);
// {
// base: 'USDT',
// quote: 'KRW',
// price: number,
// }Internally:
- It fetches
BTC/KRWandBTC/USDTviaresolveCryptoPair. - Uses
computeCrossRatefrom the domain layer to produceUSDT/KRW.
Legal FX is based on a normalized table:
interface NormalizedFiatFxRates {
base: FiatCurrencyCode; // anchor fiat (e.g. 'KRW')
quotes: Record<FiatCurrencyCode, number>; // base-per-1-quote
}LegalFiatFxMarketAdapter:
- Fetches raw data from config.endpoint.
- Normalizes it via config.transform(raw).
- Caches the normalized object for ttlMs.
- Computes any fiat↔fiat pair via simple ratios.
You can plug in your own legal FX endpoint by providing a custom transform function:
import type { NormalizedFiatFxRates, FiatFxTransform } from "fusanex";
const myFxTransform: FiatFxTransform = (raw: any): NormalizedFiatFxRates => {
// Convert your provider's JSON shape into:
// { base: 'USD', quotes: { KRW: 1300, JPY: 150, ... } }
return {
base: "USD",
quotes: {
KRW: raw.usd_krw,
JPY: raw.usd_jpy,
EUR: raw.usd_eur,
CNY: raw.usd_cny,
VND: raw.usd_vnd,
},
};
};
const fx = fusanex({
fiatFx: {
endpoint: "https://my-fx.example.com/latest",
transform: myFxTransform,
ttlMs: 60_000,
},
});- Domain types live under domain/ (assets, currencies, exchange rates).
- Ports and service live under application/.
- External integrations (Upbit, Binance, legal FX provider) live under adapter/out/.
- The main factory createFusanex lives under adapter/in/ and is wrapped by the default export in index.ts.
You can use this structure as a reference if you extend the library with more providers or new assets.