@mr-zwets/cauldron-swap-sdk
v0.1.6
Published
TypeScript SDK for trading on the Cauldron DEX on Bitcoin Cash, built with the CashScript SDK
Readme
Cauldron Swap SDK
TypeScript SDK for trading on the Cauldron DEX on Bitcoin Cash, built with the CashScript SDK.
Overview
prepareBuyTokens and prepareSellTokens accept an array of pools and optimally split the trade across them using binary search on the marginal rate of the constant product curve, so each pool ends up at the same marginal cost — minimizing total price impact. Pools that don't save enough to justify their extra transaction bytes are automatically dropped. See multi-pool.md for details on the algorithm.
Note: Pools are fetched from the Cauldron indexer API, which is a trusted third party. All token amounts are in base units (raw on-chain amounts, bigint) — BCMR decimal places are not supported.
Install
Install the cauldron-swap-sdk from NPM with:
pnpm install @mr-zwets/cauldron-swap-sdkBuy Tokens
import { getCauldronPools, prepareBuyTokens } from "@mr-zwets/cauldron-swap-sdk"
import { userTokenAddress, signer } from "./config"
const furuTokenId = "d9ab24ed15a7846cc3d9e004aa5cb976860f13dac1ead05784ee4f4622af96ea"
const amountToBuy = 100_000n
const cauldronPools = await getCauldronPools(furuTokenId)
// pass all pools — the SDK optimally splits the trade across pools
const { transactionBuilder, totalSatsCost, totalFees, effectivePricePerToken } = await prepareBuyTokens(
cauldronPools,
amountToBuy,
userTokenAddress,
signer
)
// review the effective price before broadcasting
console.log(`Cost: ${totalSatsCost} sats (${totalFees} fees), ${effectivePricePerToken} sats/token`)
const txDetails = await transactionBuilder.send()Sell Tokens
import { getCauldronPools, prepareSellTokens } from "@mr-zwets/cauldron-swap-sdk"
import { userTokenAddress, signer } from "./config"
const furuTokenId = "d9ab24ed15a7846cc3d9e004aa5cb976860f13dac1ead05784ee4f4622af96ea"
const amountToSell = 100_000n
const cauldronPools = await getCauldronPools(furuTokenId)
// pass all pools — the SDK optimally splits the trade across pools
const { transactionBuilder, totalSatsReceived, totalFees, effectivePricePerToken } = await prepareSellTokens(
cauldronPools,
amountToSell,
userTokenAddress,
signer
)
// review the effective price before broadcasting
console.log(`Receive: ${totalSatsReceived} sats (${totalFees} fees), ${effectivePricePerToken} sats/token`)
const txDetails = await transactionBuilder.send()Rate-Targeted Trading
Two helpers let you query how much liquidity is available at a given price — useful for limit-order-style UIs:
import { getCauldronPools } from "@mr-zwets/cauldron-swap-sdk"
import { computeBuyAmountBelowRate, computeSellAmountAboveRate } from "@mr-zwets/cauldron-swap-sdk"
const cauldronPools = await getCauldronPools(furuTokenId)
// How many tokens can I buy before the marginal rate exceeds 150 sats/token?
const buyableAmount = computeBuyAmountBelowRate(cauldronPools, 150n)
// How many tokens can I sell before the marginal rate drops below 80 sats/token?
const sellableAmount = computeSellAmountAboveRate(cauldronPools, 80n)
// Then feed the result into the existing prepare functions
const result = await prepareBuyTokens(cauldronPools, buyableAmount, userTokenAddress, signer)Create Pool
import { prepareCreatePool } from "@mr-zwets/cauldron-swap-sdk"
import { signer } from "./config"
const furuTokenId = "d9ab24ed15a7846cc3d9e004aa5cb976860f13dac1ead05784ee4f4622af96ea"
const satsAmount = 100_000n // BCH liquidity to seed the pool with
const tokenAmount = 100n // token base units to seed the pool with
// the signer's token address is derived automatically
const { transactionBuilder, poolContractAddress, ownerPkh } = await prepareCreatePool(
furuTokenId,
satsAmount,
tokenAmount,
signer,
)
console.log(`Pool contract: ${poolContractAddress}`)
const txDetails = await transactionBuilder.send()The transaction includes an OP_RETURN SUMMON <PKH> output so the Cauldron indexer picks up the new pool immediately.
Withdraw Liquidity
import { getCauldronPools, prepareWithdrawAll } from "@mr-zwets/cauldron-swap-sdk"
import { userTokenAddress, signer } from "./config"
const furuTokenId = "d9ab24ed15a7846cc3d9e004aa5cb976860f13dac1ead05784ee4f4622af96ea"
const poolOwnerAddress = "bitcoincash:qps99uejnueu4dsv0dd2m9u9uzxntg66nyux08wmzq"
const cauldronPools = await getCauldronPools(furuTokenId)
// filter your pool
const poolToUse = cauldronPools.find(pool => pool.owner_p2pkh_addr == poolOwnerAddress)
// prepare withdraw transaction
const { transactionBuilder } = await prepareWithdrawAll(
poolToUse,
userTokenAddress,
signer,
)
const txDetails = await transactionBuilder.send()Chipnet Usage
import { ElectrumNetworkProvider } from 'cashscript';
import { getCauldronPools, prepareBuyTokens } from "@mr-zwets/cauldron-swap-sdk"
import { userTokenAddress, signer } from "./config"
const chipnetTokenId = "53636bc8c1afbe35a7ba169eadfac0aebadeacf96954a9a066a483e885580ed4"
const amountToBuy = 100n
// fetch pools from chipnet indexer
const cauldronPools = await getCauldronPools(chipnetTokenId, 'chipnet')
// use a chipnet provider for the transaction
const provider = new ElectrumNetworkProvider('chipnet')
const { transactionBuilder } = await prepareBuyTokens(
cauldronPools,
amountToBuy,
userTokenAddress,
signer,
provider
)
const txDetails = await transactionBuilder.send()Custom Arifacts
The Cauldron contract does not have a ready-to-go CashScript artifact, so custom artifacts were created to be able to use the CashScript SDK tooling.
You can see the JSON artifacts in src/artifact and find an explanation of this in artifacts.md
Run the Tests
npm i
npm run testor using pnpm:
pnpm i
pnpm run testFuture Extensions
BCMR token metadata
The trading logic currently operates on base token units (the raw on-chain amount). The BCMR standard allows tokens to define a ticker symbol and decimal places for a higher-level display unit, but this SDK does not yet support that abstraction. Token amounts passed to prepareBuyTokens/prepareSellTokens must be in base units (and hence are BigInt type).
On-chain pool discovery
Currently pool discovery relies on the Cauldron indexer API, which is a trusted third party. Ideally pools could be discovered directly on-chain. The challenge is that Cauldron pools use P2SH, so the contract code is hidden behind a hash — you can't identify them by script fingerprinting alone. However, new pools can be discovered by their OP_RETURN marker SUMMON in the creation transaction. BCHN's bytecode pattern RPC (redeemBytecodePattern with fingerprint matching) could help identify existing pools when they are spent, since the redeem script is revealed at spend time.
