Skip to content

Flux-Point-Studios/dexter

 
 

Repository files navigation

Dexter

Customizable Typescript SDK for interacting with Cardano DEXs.

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).
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=node when 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_ADDRESS and/or DEXTER_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_URL and BLOCKFROST_PROXY_PROJECT_ID to use your proxy endpoint. If not set, it falls back to direct Blockfrost credentials (BLOCKFROST_URL and BLOCKFROST_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:

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 value

Install

NPM
npm i @fluxpointstudios/dexter
Yarn
yarn add @fluxpointstudios/dexter

Quick 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-AMM only (no API key required).
  • The CLOB/REST provider SaturnSwap is optional and disabled by default.
  • To enable the CLOB/REST provider, pass { enableSaturnClob: true } in your DexterConfig.
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:

  • ammBuildOrder returns 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_KEY or SATURN_API_TOKEN (will be sent as Authorization: Bearer <value>)

SaturnSwap by-asset inputs at a glance

  • asset: concatenation of policyId + assetNameHex (no separator). Use empty string '' for ADA.
  • direction:
    • 3 MarketBuy (spend ADA → receive token). tokenAmountSell is ADA (display units).
    • 4 MarketSell (sell token → receive ADA). tokenAmountSell is token amount (display units).
  • tokenAmountSell / tokenAmountBuy: display units (we scale by decimals internally).
  • slippage:
    • When tokenAmountBuy = 0, set slippage = null (builder treats buy=0+slippage as a hard fail).
    • To enforce minimum output, first call quote, then set tokenAmountBuy = quote.expectedBuy and slippage = e.g., 0.5.

SaturnSwap API surface in this SDK

  • Discovery:
    • SaturnSwapApi.assets(): GET /v1/aggregator/assets
    • SaturnSwapApi.orderbook(assetA, assetB): GET /v1/aggregator/orderbook
    • SaturnSwapApi.quoteByAsset(input): POST /v1/aggregator/quote
  • Build / Sign / Submit:
    • SaturnSwapApi.createOrderTransactionSimple(...): POST /v1/aggregator/simple/create-order-transaction
    • SaturnSwapApi.createOrderTransactionFromAsset(input): POST /v1/aggregator/simple/create-from-asset
    • SaturnSwapApi.signOrderTransactionAdvanced(...): POST /v1/aggregator/advanced/sign-order-transaction
    • SaturnSwapApi.submitOrderTransactionSimple(...): POST /v1/aggregator/simple/submit-order-transaction
  • Dexter convenience (in SaturnSwap):
    • quoteByAsset(input)
    • createFromAssetHex(input) → hex
    • buildFromAssetSignSubmit(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: true and (optionally) splashSdkNetwork: 'mainnet' | 'staging' in your DexterConfig to register the new adapter alongside the legacy on-chain builder.
    • Pools discovered through SplashSdk expose pool.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 Splash pools remain available; opt-in per pool if you need to migrate gradually.

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.

dexByName(string): BaseDex | undefined Grab a DEX instance by name.
Using
dexter.dexByName(Minswap.identifier)
    ...

withDataProvider(BaseDataProvider): Dexter Set where Dexter should grab liquidity pool data.

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)
    ...

withWalletProvider(BaseWalletProvider): Dexter Set who Dexter sends wallet requests to.

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_URL and BLOCKFROST_PROXY_PROJECT_ID environment variables
  • Direct mode: Set BLOCKFROST_URL (optional, defaults to mainnet) and BLOCKFROST_PROJECT_ID environment 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)
            ...
    });

withMetadataProvider(BaseMetadataProvider): Dexter Set where Dexter grabs necessary asset metadata.

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)
    ...

newFetchRequest(): FetchRequest Create new request for liquidity pool data.

For available methods on the FetchRequest instance, please see those specific docs.

Using
dexter.newFetchRequest()
    ...

newSwapRequest(): SwapRequest Create new request for a swap order.

For available methods on the SwapRequest instance, please see those specific docs.

Using
dexter.newSwapRequest()
    ...

newSplitSwapRequest(): SplitSwapRequest Create new request for a split swap order.

For available methods on the SplitSwapRequest instance, please see those specific docs.

Using
dexter.newSplitSwapRequest()
    ...

newCancelSwapRequest(): CancelSwapRequest Create new request for cancelling a swap order.

For available methods on the CancelSwapRequest instance, please see those specific docs.

Using
dexter.newCancelSwapRequest()
    ...

newSplitCancelSwapRequest(): SplitCancelSwapRequest Create new request for cancelling multiple swap orders.

For available methods on the SplitCancelSwapRequest instance, please see those specific docs.

Using
dexter.newSplitCancelSwapRequest()
    ...

More Docs

About

Customizable Typescript SDK for interacting with Cardano DEXs

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • TypeScript 85.4%
  • JavaScript 14.6%