@fluxpointstudios/dexter
v0.0.19
Published
Customizable Typescript SDK for interacting with Cardano DEXs (FluxPoint fork with SaturnSwap REST)
Readme
What You Can Do
- Pull Liquidity Pools from DEX APIs or On-chain using Blockfrost / Kupo
- Submit and cancel swap orders
- Submit split swap orders across multple DEXs
- Build your own data, wallet, or asset metadata providers to plug into Dexter
- Build swap datums given specific parameters using Dexters Definition Builder
- Load wallets using a seedphrase or CIP-30 interface using @lucid-evolution/lucid
CSWAP (Hybrid AMM Orderbook)
- On-chain pool discovery via CSWAP Dex Pool address (ADA pairs only).
- Fee model:
- Pool fee from pool datum
lp_fee_10k(e.g., 85 → 0.85%). - Batcher fee: 690,000 lovelace.
- Per-order min deposit: 2,000,000 lovelace (returned upon fill/cancel).
- Platform fee per 10k: 15 (applied to target min output in datum).
- Pool fee from pool datum
import { Dexter, CSwap, BlockfrostProvider, LucidProvider } from '@fluxpointstudios/dexter';
const dexter = new Dexter();
const wallet = new LucidProvider();
await wallet.loadWalletFromSeedPhrase(
['...seed words...'],
{ accountIndex: 0 },
{ url: 'https://cardano-mainnet.blockfrost.io/api/v0', projectId: '<BLOCKFROST_PROJECT_ID>' }
);
dexter.withWalletProvider(wallet);
const data = new BlockfrostProvider(
{ url: 'https://cardano-mainnet.blockfrost.io/api/v0', projectId: '<BLOCKFROST_PROJECT_ID>' }
);
dexter.withDataProvider(data);
const cswap = dexter.dexByName(CSwap.identifier) as CSwap;
const pools = await cswap.liquidityPools(data);
const pool = pools.find(p => p.assetA === 'lovelace') || pools[0];
const tx = await dexter.newSwapRequest()
.forLiquidityPool(pool)
.withSwapInToken('lovelace')
.withSwapInAmount(2_000_000n) // 2 ADA
.withSlippagePercent(0.5)
.complete();
// sign and submit as needed:
// await tx.sign(); const hash = await tx.submit();CSWAP Pricing helpers
This is the AMM pool’s implied price. Orderbook best bid/ask could differ; we can extend to read order UTxOs at the orderbook address to synthesize a live spread if needed.
import { getAmmImpliedPrice, getOrderbookTopOfBook, Asset } from '@fluxpointstudios/dexter';
// AMM-implied (from pool reserves)
const pools = await dexter.newFetchRequest()
.onDexs(CSwap.identifier)
.getLiquidityPools();
const pool = pools.find(p => p.assetA === 'lovelace')!;
const { adaPerToken, tokenPerAda } = getAmmImpliedPrice(pool);
// Orderbook top-of-book (best bid/ask)
const token = Asset.fromIdentifier('<policyId><assetNameHex>');
const top = await getOrderbookTopOfBook(dexter.dataProvider!, token.identifier());
console.log({ adaPerToken, tokenPerAda, top });Notes
- You may need to use the flag
--experimental-specifier-resolution=nodewhen building your project to correctly import Dexter - All figures/parameters represented as a bigint are denominated in lovelaces
- Optional platform fee hook: set
DEXTER_PLATFORM_FEE_ADDRESSand/orDEXTER_PLATFORM_FEE_LOVELACE(lovelace bigint string) to force every swap request to include a fixed ADA payment to your treasury. Defaults are always enabled (2 ADA to Flux Point Studios) in this fork, so override these env vars if you need a different destination or amount. - Blockfrost Proxy Support: Dexter now supports proxy-based Blockfrost configuration via environment variables. Set
BLOCKFROST_PROXY_URLandBLOCKFROST_PROXY_PROJECT_IDto use your proxy endpoint. If not set, it falls back to direct Blockfrost credentials (BLOCKFROST_URLandBLOCKFROST_PROJECT_ID). This is particularly useful for production deployments with centralized API management.
Token Decimals Resolution
Important: Asset.decimals values in @fluxpointstudios/dexter are populated from DEX API metadata and are NON-AUTHORITATIVE hints only. DEX metadata can be incorrect or change without notice.
For any safety-critical calculations (pricing, slippage, order sizing, etc.), consumers should resolve authoritative decimals via:
- Cardano CF Token Registry
- On-chain metadata (CIP-25/CIP-68)
- Your own decimals resolver service
The decimals values provided by Dexter are suitable for:
- Display/cosmetic purposes
- Rough analytics and pool comparisons
- Initial hints that can be overridden by your resolver
They should NOT be trusted for:
- Final order sizing calculations
- Slippage enforcement
- Risk-critical pricing logic
Example pattern for consumers:
// Dexter provides decimals as hints
const pool = await dexter.newFetchRequest().onDexs('Minswap').getLiquidityPools();
const asset = pool[0].assetB as Asset;
// Your app should resolve authoritative decimals
const authoritativeDecimals = await yourDecimalsResolver.resolve(asset.identifier());
asset.decimals = authoritativeDecimals; // Override with trusted valueInstall
NPM
npm i @fluxpointstudios/dexterYarn
yarn add @fluxpointstudios/dexterQuick Start
const dexterConfig: DexterConfig = {
shouldFetchMetadata: true, // Whether to fetch asset metadata (Best to leave this `true` for accurate pool info)
shouldFallbackToApi: true, // Only use when using Blockfrost or Kupo as data providers. On failure, fallback to the DEX API to grab necessary data
shouldSubmitOrders: false, // Allow Dexter to submit orders from swap requests. Useful during development
metadataMsgBranding: 'Dexter', // Prepend branding name in Tx message
enableSaturnClob: false, // Enable Saturn CLOB REST adapter
enableSplashSdk: false, // Register SplashSdk adapter (requires Splash SDK deps)
splashSdkNetwork: 'mainnet', // 'mainnet' | 'staging' for Splash SDK integration
};
const requestConfig: RequestConfig = {
timeout: 5000, // How long outside network requests have to reply
proxyUrl: '', // URL to prepend to all outside URLs. Useful when dealing with CORs
retries: 3, // Number of times to reattempt any outside request
};
const dexter: Dexter = new Dexter(dexterConfig, requestConfig);
// Basic fetch example
dexter.newFetchRequest()
.onAllDexs()
.getLiquidityPools()
.then((pools: LiquidityPool[]) => {
console.log(pools);
});
// Example loading wallet to be used in a swap
const lucidProvider: BaseWalletProvider = new LucidProvider();
lucidProvider
.loadWallet(cip30Interface, {
url: 'https://cardano-mainnet.blockfrost.io/api/v0',
projectId: '<blockfrost-project-id>'
})
.then((walletProvider: BaseWalletProvider) => {
dexter.withWalletProvider(walletProvider)
.newFetchRequest()
...
});Saturn defaults for SaturnSwap
- By default, Dexter registers the AMM facade provider
SaturnSwap-AMMonly (no API key required). - The CLOB/REST provider
SaturnSwapis optional and disabled by default. - To enable the CLOB/REST provider, pass
{ enableSaturnClob: true }in yourDexterConfig.
import { Dexter, SaturnSwapAMM } from '@fluxpointstudios/dexter';
const dexter = new Dexter({ enableSaturnClob: false }); // default
const amm = dexter.dexByName(SaturnSwapAMM.identifier);SaturnSwap-AMM (virtual AMM facade)
For apps that prefer AMM-like math and pool discovery (like Minswap/WingRiders), you can use the Saturn AMM facade:
import { Dexter, SaturnSwapAMM } from '@fluxpointstudios/dexter';
const dexter = new Dexter();
const amm = dexter.dexByName(SaturnSwapAMM.identifier) as SaturnSwapAMM;
// Pull AMM pools via REST
const pools = await amm.liquidityPools();
console.log('AMM pools', pools.length);
// AMM math (constant product) works like other providers
const pool = pools[0];
const estimated = amm.estimatedReceive(pool, 'lovelace', 1_000_000n); // 1 ADA in lovelace
// Optional server quote/build (on-chain units)
const quote = await (amm.api as any).ammQuote({
poolId: pool.identifier, direction: 'in', swapInAmount: 1_000_000, slippageBps: 50
});
// Build a market swap (returns tokens in same tx). Optionally include partnerAddress for fee split.
const hex = await (amm.api as any).ammBuildOrder({
poolId: pool.identifier,
direction: 'in',
swapInAmount: 1_000_000,
slippageBps: 50,
changeAddress: '<bech32>',
partnerAddress: '<optional-partner-bech32>' // 1 ADA partner + 1 ADA platform; if omitted, 2 ADA to platform
});
// Sign and submit locally
const tx = wallet.newTransactionFromHex(hex.unsignedCborHex);
await tx.sign();
await tx.submit();Notes:
ammBuildOrderreturns unsigned CBOR; sign/submit locally with your wallet.- Spot Market swap by default.
- Server-enforced fee outputs: 2 ADA total; with
partnerAddress, 1/1 split between partner and platform; otherwise 2 ADA to platform. - Pool snapshots are cached ~1–2s; re-quote if you need a fresh snapshot for minReceive checks.
SaturnSwap (Advanced REST) [Optional]
// Configure env (example). You can also set process.env at runtime.
// SATURN_API_BASE_URL=https://api.saturnswap.io
// SATURN_API_KEY=your-api-key-or-bearer-token
// Enable the CLOB provider when constructing Dexter
const dexter = new Dexter({ enableSaturnClob: true });
const wallet = new LucidProvider();
await wallet.loadWallet(cip30Interface, {
url: 'https://cardano-mainnet.blockfrost.io/api/v0',
projectId: '<blockfrost-project-id>'
});
dexter.withWalletProvider(wallet);
// A) High-level: quote and build-by-asset (no need to pick poolId)
const saturn = dexter.dexByName('SaturnSwap');
// By-asset quote (no Authorization required)
const quote = await (saturn as any).quoteByAsset({
asset: '<policyId><assetNameHex>', // '' if ADA
direction: 3, // 3 = MarketBuy (ADA → token), 4 = MarketSell (token → ADA)
tokenAmountSell: 1.0, // display units (ADA or token), not on-chain units
tokenAmountBuy: 0, // 0 with slippage=null lets builder choose fills
slippage: null // set a number (e.g., 0.5) only if you also set tokenAmountBuy
});
console.log('quote:', quote);
// Build from asset (Authorization required via SATURN_API_KEY)
// Returns first unsigned tx hex if buildable at this moment
const hex = await (saturn as any).createFromAssetHex({
asset: '<policyId><assetNameHex>',
direction: 3,
tokenAmountSell: 1.0, // try 1–2 ADA (very small sizes like 0.5 ADA can be rejected by min-output rules)
tokenAmountBuy: 0,
slippage: null,
paymentAddress: wallet.address()
});
// Sign + submit locally (or use advanced/sign for pre-cosigned flows)
if (hex) {
const tx = wallet.newTransactionFromHex(hex);
await tx.sign();
await tx.submit();
console.log('Submitted:', tx.hash);
}
// B) Lower-level: build via specific poolId (if you want to route yourself)
// const input = {
// paymentAddress: wallet.address(),
// limitOrderComponents: [
// { poolId: '...', tokenAmountSell: 1000000, tokenAmountBuy: 500000, limitOrderType: 0, version: 1 }
// ]
// };
// const txHash = await (saturn as any).buildSignSubmitViaApi(input, wallet);
// console.log('Submitted:', txHash);Environment variables:
SATURN_API_BASE_URL(e.g.,https://api.saturnswap.io)SATURN_API_KEYorSATURN_API_TOKEN(will be sent asAuthorization: Bearer <value>)
SaturnSwap by-asset inputs at a glance
asset: concatenation ofpolicyId + assetNameHex(no separator). Use empty string''for ADA.direction:3MarketBuy (spend ADA → receive token).tokenAmountSellis ADA (display units).4MarketSell (sell token → receive ADA).tokenAmountSellis token amount (display units).
tokenAmountSell/tokenAmountBuy: display units (we scale by decimals internally).slippage:- When
tokenAmountBuy = 0, setslippage = null(builder treats buy=0+slippage as a hard fail). - To enforce minimum output, first call
quote, then settokenAmountBuy = quote.expectedBuyandslippage = e.g., 0.5.
- When
SaturnSwap API surface in this SDK
- Discovery:
SaturnSwapApi.assets(): GET/v1/aggregator/assetsSaturnSwapApi.orderbook(assetA, assetB): GET/v1/aggregator/orderbookSaturnSwapApi.quoteByAsset(input): POST/v1/aggregator/quote
- Build / Sign / Submit:
SaturnSwapApi.createOrderTransactionSimple(...): POST/v1/aggregator/simple/create-order-transactionSaturnSwapApi.createOrderTransactionFromAsset(input): POST/v1/aggregator/simple/create-from-assetSaturnSwapApi.signOrderTransactionAdvanced(...): POST/v1/aggregator/advanced/sign-order-transactionSaturnSwapApi.submitOrderTransactionSimple(...): POST/v1/aggregator/simple/submit-order-transaction
- Dexter convenience (in
SaturnSwap):quoteByAsset(input)createFromAssetHex(input)→ hexbuildFromAssetSignSubmit(input, wallet)→ txHash- Legacy pool-based helpers:
buildSwapOrder(...),createSimpleOrderHexViaApi(...),buildSignSubmitViaApi(...)
Troubleshooting tips
Pool-specific depth: an asset may have bids/asks overall but your chosen pool could be empty at build time. Use
quoteByAsset(auto-routes to a spendable pool).Small sizes: very small ADA spends (e.g., 0.5) can fail due to min-output/fee constraints—try ≥1–2 ADA.
Units: always send display units (ADA or token). Do not pre-scale to on-chain units.
Splash SDK Adapter (
SplashSdk)- Set
enableSplashSdk: trueand (optionally)splashSdkNetwork: 'mainnet' | 'staging'in yourDexterConfigto register the new adapter alongside the legacy on-chain builder. - Pools discovered through
SplashSdkexposepool.dex === 'SplashSdk'. Route swap requests to that identifier to build orders via@splashprotocol/sdk. - The adapter consumes
@splashprotocol/sdk,@splashprotocol/core,@splashprotocol/api, and@splashprotocol/cml-builder. Ensure these packages are installed (they are bundled with this repo). - Existing
Splashpools remain available; opt-in per pool if you need to migrate gradually.
- Set
Dexter API
All providers outlined below are modular, so you can extend the 'base' of the specific provider you want to supply, and provide it to Dexter with one of the methods below.
Using
dexter.dexByName(Minswap.identifier)
...By default, Dexter will use the DEX APIs to grab information. However, you can use Blockfrost or Kupo to supply your own data.
Using
const provider: BaseDataProvider = new BlockfrostProvider(
{
url: 'https://cardano-mainnet.blockfrost.io/api/v0',
projectId: '<blockfrost-project-id>',
}
);
dexter.withDataProvider(provider)
...At this time, Dexter only supplies a Mock wallet provider & a Lucid provider. Behind the scenes, the lucid provider leverages @lucid-evolution/lucid to manage your wallet & create transactions.
Blockfrost Configuration: The LucidProvider automatically resolves Blockfrost configuration from environment variables:
- Proxy mode (preferred for production): Set
BLOCKFROST_PROXY_URLandBLOCKFROST_PROXY_PROJECT_IDenvironment variables - Direct mode: Set
BLOCKFROST_URL(optional, defaults to mainnet) andBLOCKFROST_PROJECT_IDenvironment variables
You can still pass explicit BlockfrostConfig objects, but if omitted, the provider will use the environment-based resolution.
Using
const provider: BaseWalletProvider = new LucidProvider();
const seedphrase: string[] = ['...'];
const blockfrostConfig: BlockfrostConfig = {
url: 'https://cardano-mainnet.blockfrost.io/api/v0',
projectId: '<blockfrost-project-id>',
};
provider.loadWalletFromSeedPhrase(seedphrase, blockfrostConfig)
.then((walletProvider: BaseWalletProvider) => {
dexter.withWalletProvider(walletProvider)
...
});By default, Dexter will use the Cardano Token Registry for grabbing
asset metadata. You can extend the BaseMetadataProvider interface to provide your own metadata.
Using
const provider: BaseMetadataProvider = new TokenRegistryProvider();
dexter.withMetadataProvider(provider)
...For available methods on the FetchRequest instance, please see those specific
docs.
Using
dexter.newFetchRequest()
...For available methods on the SwapRequest instance, please see those specific
docs.
Using
dexter.newSwapRequest()
...For available methods on the SplitSwapRequest instance, please see those specific
docs.
Using
dexter.newSplitSwapRequest()
...For available methods on the CancelSwapRequest instance, please see those specific
docs.
Using
dexter.newCancelSwapRequest()
...For available methods on the SplitCancelSwapRequest instance, please see those specific
docs.
Using
dexter.newSplitCancelSwapRequest()
...