npm package: @saturnbtcio/pool-sdk
Description: A TypeScript client library for interacting with Bitcoin Rune liquidity pools on Saturn.   This SDK provides a simple, high-level interface to:
Author: Saturn BTC
Latest version: 0.0.45

# @saturnbtcio/pool-sdk

A TypeScript client library for interacting with Bitcoin Rune liquidity pools on Saturn.  
This SDK provides a simple, high-level interface to:

- **Initialize a new liquidity pool**
- **Add shards** (on-chain dust-limit UTXOs backing pool shares)
- **Open and increase positions** (deposit Rune + BTC into a pool)
- **Decrease liquidity** and **withdraw tokens** from an existing position
- **Swap BTC ↔ Rune** trades via on-chain instructions

<!-- Under the hood it combines:

- 🪙 **Saturn Arch** (`@saturnbtcio/arch-sdk`) for submitting Solana-style Arch transactions
- 📜 **Borsh-encoded instruction payloads** from `@saturnbtcio/pool-serde-sdk`
- 💰 **PSBT builders and fee calculators** from `@saturnbtcio/psbt`
- 🔑 **Rune minting & token IDs** via `@saturnbtcio/ordinals-lib`

--- -->

## Installation

```bash
npm install @saturnbtcio/pool-sdk

yarn add @saturnbtcio/pool-sdk

pnpm install @saturnbtcio/pool-sdk
```

## SaturnSdk

The `SaturnSdk` class is your main entry point to driving all pool-related instructions on Saturn. You instantiate it once with your program/account IDs and three “provider” implementations, then call into its methods to build & send transactions.

The configuration you need to pass to the SaturnSdk is the following:

| Field                      | Type                   | Description                                                      |
| -------------------------- | ---------------------- | ---------------------------------------------------------------- |
| `programAddress`           | `string`               | On-chain program ID (PDA) to which all instructions are sent     |
| `programAccount`           | `string`               | 32-byte hex pubkey for your pool program account                 |
| `network`                  | `Network`              | Bitcoin network enum (`Mainnet`/`Testnet`/`Regtest`)             |
| `mempoolInfoOracleAccount` | `string`               | PDA that holds live mempool fee & vsize data                     |
| `feeRateOracleAccount`     | `string`               | PDA that holds on-chain fee-rate oracle                          |
| `maxTxSize`                | `number`               | Maximum allowed PSBT vsize (enforced client-side)                |
| `archProvider`             | `IArchProvider`        | RPC adapter for Arch (Solana-style) transactions                 |
| `bitcoinProvider`          | `IBitcoinProvider`     | RPC adapter for Bitcoin wallet, mempool, blockheight lookups     |
| `indexerProvider`          | `IPoolIndexerProvider` | Adapter for fetching pool & position state from your indexer API |

```bash

const sdk = new SaturnSdk({
  programAddress:         '… your on-chain program address …',
  programAccount:         '… your program’s account pubkey …',
  network:                Network.Regtest,        // or Mainnet/Testnet
  mempoolInfoOracleAccount: '… oracle PDAs …',
  feeRateOracleAccount:     '… oracle PDAs …',
  maxTxSize:              10_000,                // enforce PSBT size limits
  archProvider:           new ArchRpc(...),
  bitcoinProvider:        new BtcRpc(...),
  indexerProvider:        new IndexerApi(...),
})
```

## Providers

The SDK delegates external concerns to three pluggable “provider” interfaces. You must supply concrete implementations when constructing SaturnSdk.

### Bitcoin provider

| Method                   | Return Type                | Description                                                                        |
| ------------------------ | -------------------------- | ---------------------------------------------------------------------------------- |
| `getRecommendedFees()`   | `Promise<FeesRecommended>` | Fetches fee estimates for constructing transactions.                               |
| `getLatestBlockHeight()` | `Promise<number>`          | Returns the current chain height.                                                  |
| `getMempoolInfo(txIds)`  | `Promise<MempoolInfoMap>`  | Looks up mempool entries by TXID; returns a map of \`TxId → MempoolEntry           |
| `getWallet(address)`     | `Promise<Wallet>`          | Returns a `Wallet` object containing UTXOs, balances, etc., for the given address. |

### Arch provider

| Method                | Return Type       | Description                                  |
| --------------------- | ----------------- | -------------------------------------------- |
| `getAccountAddress()` | `Promise<string>` | Fetches the address for the provided pubkey. |

### Indexer provider

| Method                                             | Return Type                            | Description                                                    |
| -------------------------------------------------- | -------------------------------------- | -------------------------------------------------------------- |
| `getPoolById(poolId)`                              | `Promise<IdentifiableLiquidityPool>`   | Fetches a single pool by its on-chain identifier.              |
| `getPoolsByTokenIds(token0Id, token1Id, feeTier?)` | `Promise<IdentifiableLiquidityPool[]>` | Fetches all pools matching a token pair and optional fee tier. |
| `getCollection(token)`                             | `Promise<Collection>`                  | Loads an on-chain collection entry.                            |

## Instructions

### Initialize Pool

Bootstraps a fresh liquidity pool for a Rune <> BTC pair.
After this completes, your pool program owns N “shard” PDAs plus one pool-config PDA, each pre-funded with a dust UTXO and ready to accept swaps, adds, and withdrawals.

#### PSBT Method: `initPoolCreateAccountPsbt`

**What it does:**

- Computes the addresses of 1 + shardsLength PDAs (the “pool-config” PDA + each shard PDA).
- Adds a dust output to each PDA.
- Adds an output back to your Arch account to cover the “state-change” fee.
- Collects your wallet UTXOs and builds a PSBT to fund all of the above + miner fees.

Request object `InitPoolCreateAccountPsbtRequest`:

| Field          | Type                          | Description                                                      |
| -------------- | ----------------------------- | ---------------------------------------------------------------- |
| `address`      | `string`                      | Your Bitcoin address (receives change & state-fee refund)        |
| `publicKey`    | `string`                      | X-only pubkey hex of your Arch account (32 bytes → 64 hex chars) |
| `feeRate`      | `bigint`                      | sats per vbyte                                                   |
| `shardsLength` | `number`                      | Number of pool shards (_N_)                                      |
| `feeTier`      | `number`                      | Numerical fee tier (used in swap math, e.g. 20 000)              |
| `token0`       | `{block: bigint, tx: number}` | Your Rune collection ID                                          |
| `token1`       | `{block: bigint, tx: number}` | Always `{0n, 0}` for BTC                                         |

Example of usage:

```bash
const psbtResponse = await sdk.createAccounts.initPoolCreateAccountPsbt({
  address:      myBtcAddr,
  publicKey:    xOnlyHex,
  feeRate:      20_000n,
  shardsLength: 10,
  feeTier:      20_000,
  token0:       { block: 123n, tx: 456 },
  token1:       { block:   0n, tx:   0 },
});
```

#### Message Method: `initializePoolMessage`

**What it does:**

- Verifies no existing pool exists for your (token0, token1, feeTier).
- Confirms your Rune UTXO is deeply confirmed (via the indexer + Bitcoin).
- Parses your signed PSBT to collect the exact UTXOs you funded.
- Constructs an InitializePool Arch message ready to send on-chain.

Request object: `InitializePoolMessageRequest`:

| Field              | Type     | Description                                         |
| ------------------ | -------- | --------------------------------------------------- |
| `address`          | `string` | Same Bitcoin address you used above                 |
| `publicKey`        | `string` | X-only pubkey hex (must match the PSBT funding)     |
| `randomNumber`     | `number` | 1 byte randomness to prevent replay                 |
| `feeRate`          | `bigint` | Same sats/vbyte you used to build the PSBT          |
| `signedPsbt`       | `string` | Base64 encoding of the fully signed PSBT            |
| `token0`           | `string` | `"block:tx"` of your Rune collection                |
| `token1`           | `string` | `"0:0"` _(must be BTC)_                             |
| `feeTier`          | `number` | Same fee tier                                       |
| `minConfirmations` | `number` | Minimum confirmations for the Rune etch (usually 6) |
| `shardsLength`     | `number` | Number of pool shards                               |

Example of usage:

```bash
const initMsg = await sdk.initializePool.initializePoolMessage({
  address:          myBtcAddr,
  publicKey:        xOnlyHex,
  randomNumber:     Math.floor(Math.random() * 256),
  feeRate:          20_000n,
  signedPsbt:       signedPsbtBase64,
  token0:           '123:456',
  token1:           '0:0',
  feeTier:          20_000,
  minConfirmations:  6,
  shardsLength:     10,
});
```

### Add Pool Shards

Injects N new “shard” PDAs into an already‐initialized pool.
Each new shard PDA gets a dust UTXO, and the pool program can use them for swaps, adds, and withdrawals.

#### PSBT Method: `addPoolShardsCreateAccountPsbt`

**What it does:**

- Fetches your pool state from the indexer (must exist).
- Generates N new shard PDAs via the same PDA derivation used in initialization.
- Adds a dust‐sats output for each new shard.
- Adds a state‐change refund output back to your Arch account.
- Selects your wallet UTXOs to fund the total dust + miner fees.
- Builds a PSBT ready for you to sign & broadcast.

Request object: `AddPoolShardsCreateAccountPsbtRequest`:

| Field          | Type     | Description                            |
| -------------- | -------- | -------------------------------------- |
| `address`      | `string` | Your Bitcoin change address            |
| `publicKey`    | `string` | X-only pubkey hex of your Arch account |
| `feeRate`      | `bigint` | sats per vbyte                         |
| `shardsLength` | `number` | Number of shards to add                |
| `poolId`       | `string` | Identifier of the existing pool        |

Example of usage:

```bash
const { psbt } = await sdk.createAccounts.addPoolShardsCreateAccountPsbt({
  address:      myBtcAddr,
  publicKey:    xOnlyHex,
  feeRate:      20_000n,
  shardsLength: 5,
  poolId:       "abc123…",
});
```

#### Message Method: `addPoolShardsMessage`

**What it does:**

- Validates the same pool still exists and your fee tier/collection is unchanged.
- Parses your signed PSBT to identify the exact UTXOs you funded.
- Constructs an AddPoolShards Arch message with all new shard pubkeys + UTXO proofs.

Request object: `AddPoolShardsMessageRequest`:

| Field          | Type     | Description                               |
| -------------- | -------- | ----------------------------------------- |
| `address`      | `string` | Same Bitcoin address used for PSBT change |
| `publicKey`    | `string` | X-only pubkey hex                         |
| `randomNumber` | `number` | 1 byte randomness                         |
| `feeRate`      | `bigint` | sats per vbyte                            |
| `signedPsbt`   | `string` | Base64 of your fully signed PSBT          |
| `poolId`       | `string` | Pool identifier                           |
| `shardsLength` | `number` | Number of shards you added                |

Example of usage:

```bash
const msg = await sdk.addPoolShards.addPoolShardsMessage({
  address:      myBtcAddr,
  publicKey:    xOnlyHex,
  randomNumber: Math.floor(Math.random()*256),
  feeRate:      20_000n,
  signedPsbt:   signedPsbtBase64,
  poolId:       "abc123…",
  shardsLength: 5,
});
```

### Increase Liquidity

Deposit Rune & BTC into an existing pool—either opening a new position or topping up an existing one.

- **Open Position**: Lock fresh Rune + BTC into the pool and mint you a new position PDA.
- **Add Liquidity**: Send additional Rune + BTC into your existing position PDA.

#### PSBT Method: `buildPsbtToSendFundsToProgram`

Used for both open position and increase liquidity instructions.

**What it does:**

- Validates your pool & collection state via the indexer.
- Selects your Rune UTXOs (and optional payment address UTXOs) to meet:
  - `collectionAmount` of Rune
  - `btcAmount` + program‐side state‐change fee
- Construct a PSBT with:
  - Inputs for Rune UTXOs
  - Change & refund outputs back to you
  - A dust‐sats output to the pool program for each asset
  - (If includeAccountUtxo=true) a dust UTXO for a new Position PDA
- Estimates final vsize & flags if it’s under your maxTxSize limit.

Request object: `IncreaseLiquidityRequest`

| Field                | Type           | Description                                                          |
| -------------------- | -------------- | -------------------------------------------------------------------- |
| `runeAddress`        | `string`       | Your Bitcoin address holding Rune UTXOs                              |
| `runePublicKey`      | `string`       | X-only hex pubkey for your Arch account                              |
| `paymentAddress?`    | `string\|null` | Optional separate address to pay BTC part of the fee                 |
| `paymentPublicKey?`  | `string`       | X-only hex pubkey for your payment address, if any                   |
| `feeRate`            | `bigint`       | sats per vbyte                                                       |
| `collectionAmount`   | `bigint`       | amount of Rune to deposit                                            |
| `btcAmount`          | `bigint`       | amount of BTC (sats) to deposit                                      |
| `includeAccountUtxo` | `boolean`      | `true` to open a new position PDA; `false` to top up an existing one |
| `poolId`             | `string`       | Identifier of the target pool                                        |

Example of usage:

```bash
const { tx, account, txSizeIsSmallerThanMaxTxSize } =
  await sdk.increaseLiquidity.buildPsbtToSendFundsToProgram({
    runeAddress:      wallet.addr,
    runePublicKey:    xOnlyHex,
    paymentAddress:   null,
    paymentPublicKey: undefined,
    feeRate:          20_000n,
    collectionAmount: 10_000n,
    btcAmount:        10_000n,
    includeAccountUtxo: true,    // open new position
    poolId:           'pool123…',
  });
```

#### Message Creation - Opening a New Position: `createOpenPositionMessage`

Use this after you’ve funded the pool-controlled address with your signed PSBT.

Request object: `OpenPositionMessageRequest`

| Field          | Type     | Description                                       |
| -------------- | -------- | ------------------------------------------------- |
| `signedPsbt`   | `string` | Base64 of your fully‐signed PSBT                  |
| `amount0`      | `number` | Rune quantity                                     |
| `amount1`      | `number` | BTC (sats) quantity                               |
| `randomNumber` | `number` | 1-byte randomness                                 |
| …              | …        | (See common `PoolSdkMessageRequest` fields above) |

Example of usage:

```bash
const { message, utxosInfo } =
  await sdk.increaseLiquidity.createOpenPositionMessage({
    runeAddress:   wallet.addr,
    runePublicKey: xOnlyHex,
    paymentAddress: null,
    randomNumber:   42,
    feeRate:        20_000n,
    signedPsbt:     signedPsbtBase64,
    poolId:         'pool123…',
    amount0:        10_000,  // Rune (integer)
    amount1:        10_000,  // BTC sats (integer)
    mergeUtxoPsbt:  undefined,
  });
```

#### Message Creation - Increasing Liquidity: `createIncreaseLiquidityMessage`

Request object: `IncreaseLiquidityMessageRequest`

| Field              | Type     | Description                                       |
| ------------------ | -------- | ------------------------------------------------- |
| `positionPubKey`   | `string` | X-only hex pubkey of your position PDA            |
| `collectionAmount` | `bigint` | Rune being added                                  |
| `btcAmount`        | `bigint` | BTC (sats) being added                            |
| …                  | …        | (See common `PoolSdkMessageRequest` fields above) |

Example of usage:

```bash
const { message, utxosInfo } =
  await sdk.increaseLiquidity.createIncreaseLiquidityMessage({
    runeAddress:    wallet.addr,
    runePublicKey:  xOnlyHex,
    paymentAddress: null,
    randomNumber:   7,
    feeRate:        20_000n,
    signedPsbt:     signedPsbtBase64,
    poolId:         'pool123…',
    positionPubKey: positionPdaHex,
    collectionAmount: 5_000n,
    btcAmount:        5_000n,
    mergeUtxoPsbt:    undefined,
  });
```

### Decrease Liquidity

The **Decrease Liquidity** instruction withdraws a specified amount of liquidity from a position in a Saturn pool, returning the underlying tokens (token0 and token1) to the user’s addresses. It also handles payment of any required transaction fees, either from the pool’s own reserves or by sourcing funds from the user via UTXO or a signed PSBT.

#### Message Creation: `createDecreaseLiquidityMessage`

Builds the serialized Arch transaction message for decreasing liquidity, handling fee payment via one of three methods (`none`, `fee_utxo`, `signed_psbt`).

**What it does:**

- Validates pool & position state via the indexer and your request parameters.
- Calculates the fee based on shard count, output types, and provided `feeRate`.
- Selects shards to remove liquidity from and (if needed) locates or verifies UTXOs/PSBT for fee payment.
- Constructs the `DecreaseLiquidityInstruction` and returns the raw Arch message.

**Request object:** `DecreaseLiquidityMessageRequest`

| Field                   | Type                             | Description                                                   |
| ----------------------- | -------------------------------- | ------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------- |
| `runeAddress`           | `string`                         | Your Arch‐controlled Bitcoin address holding Rune UTXOs       |
| `runePublicKey`         | `string`                         | X-only hex pubkey for your Arch account                       |
| `paymentAddress?`       | `string \| null`                 | Optional address to pay BTC fees (if not using pool reserves) |
| `paymentPublicKey?`     | `string \| undefined`            | X-only hex pubkey corresponding to `paymentAddress`           |
| `randomNumber`          | `number`                         | 1-byte randomness nonce                                       |
| `feeRate`               | `bigint`                         | Fee rate in sats per byte                                     |
| `poolId`                | `string`                         | Identifier of the pool (pubkey)                               |
| `positionPubKey`        | `string`                         | X-only hex pubkey of your position PDA                        |
| `liquidityAmount`       | `bigint`                         | Amount of liquidity to withdraw                               |
| `minToken0`             | `bigint`                         | Minimum amount of Rune (token0) to receive                    |
| `minToken1`             | `bigint`                         | Minimum amount of BTC (token1) to receive                     |
| `withdrawAddressToken0` | `string`                         | Bitcoin address to receive Rune                               |
| `withdrawAddressToken1` | `string`                         | Bitcoin address to receive BTC                                |
| `paymentMethod`         | `DecreaseLiquidityPaymentMethod` | `{ type: 'none' }`                                            | `{ type: 'fee_utxo'; feeUtxo: UtxoMetaData }` | `{ type: 'signed_psbt'; signedPsbt: string }` |

#### Example Usage

##### 1) Pool covers fees (`none`)

```ts
const msgNone = await sdk.decreaseLiquidity.createDecreaseLiquidityMessage({
  runeAddress, // your Arch address
  runePublicKey, // x-only hex pubkey
  paymentAddress: null,
  paymentPublicKey: undefined,
  randomNumber: 0,
  feeRate: 20_000n,
  poolId: 'pool123…',
  positionPubKey: positionPdaHex,
  liquidityAmount: 5_000n,
  minToken0: 10n,
  minToken1: 200n,
  withdrawAddressToken0: wallet.addr,
  withdrawAddressToken1: wallet.addr,
  paymentMethod: { type: 'none' },
});
```

#### PSBT Funding with ArchWalletSdk

Before calling `createDecreaseLiquidityMessage` with `fee_utxo` or `signed_psbt`, you must deposit sats into the pool’s Arch-controlled address and obtain a PSBT.

In order to do that use the `@saturnbtcio/arch-wallet-sdk` and the method:

Usage:

```ts
import { ArchWalletSdk } from '@saturnbtcio/arch-wallet-sdk';
import { Signer } from '@scure/btc-signer';

const archWalletSdk = new ArchWalletSdk({
  /* your config */
});
const signer = new Signer(secretKey, 'regtest');

const wallet = await signerWallet.toSdkWallet();
const xOnlyHex = Buffer.from(signer.getPublicKey()).toString('hex');

const { psbt } =
  await archWalletSdk.archWalletManager.depositFundsToArchControlledAddress({
    runeAddress: wallet.address,
    runePublicKey: xOnlyHex,
    paymentAddress: null,
    paymentPublicKey: null,
    amount: BigInt(10_000), // sats to cover fees
    feeRate, // sats per byte
  });
```

##### 2) UTXO‐based fee payment (fee_utxo)

```ts
const msgFeeUtxo = await sdk.decreaseLiquidity.createDecreaseLiquidityMessage({
  runeAddress,
  runePublicKey,
  paymentAddress: null,
  paymentPublicKey: undefined,
  randomNumber: 42,
  feeRate: 20_000n,
  poolId: 'pool123…',
  positionPubKey: positionPdaHex,
  liquidityAmount: 5_000n,
  minToken0: 10n,
  minToken1: 200n,
  withdrawAddressToken0: wallet.addr,
  withdrawAddressToken1: wallet.addr,
  paymentMethod: {
    type: 'fee_utxo',
    feeUtxo: { txid: 'abcd1234…', vout: 0 },
  },
});
```

##### 3) Signed‐PSBT fee payment (signed_psbt)

```ts
const msgSignedPsbt =
  await sdk.decreaseLiquidity.createDecreaseLiquidityMessage({
    runeAddress,
    runePublicKey,
    paymentAddress: null,
    paymentPublicKey: undefined,
    randomNumber: 99,
    feeRate: 20_000n,
    poolId: 'pool123…',
    positionPubKey: positionPdaHex,
    liquidityAmount: 5_000n,
    minToken0: 10n,
    minToken1: 200n,
    withdrawAddressToken0: wallet.addr,
    withdrawAddressToken1: wallet.addr,
    paymentMethod: {
      type: 'signed_psbt',
      signedPsbt: '<base64-encoded PSBT>',
    },
  });
```

### Swap BTC → Rune

Swap BTC for Rune tokens in a Saturn pool-exact‐in swap (you specify BTC in, receive ≥ Rune out).

- **Exact‐In Swap**: Trade `amountIn` BTC (sats) for at least `amountOut` Rune tokens.
- **Fee Handling**: All fees are paid via the BTC inputs in the PSBT; no separate fee parameter.

#### PSBT Method: `buildBtcToRunePsbt`

Constructs a PSBT funding the swap and selecting the best shards to source Rune.

**What it does:**

- Validates your pool & wallet state and requested amounts.
- Fetches pool info & sharding metadata from the indexer.
- Selects shards to remove Rune from (`amountOut`).
- Splits & adjusts Rune amounts per‐shard to avoid on‐chain limits.
- Builds a PSBT with:
  - Outputs to each shard PDA for state update
  - Pointer output for program‐side Rune accounting
  - Rune output to your address (`amountOut`)
  - BTC funding input(s) to cover `amountIn` + dust + fees
- Returns the raw PSBT, its input UTXOs, and the list of shard pubkeys.

**Request object:** `BtcToRunePsbtRequest`

| Field              | Type                  | Description                             |
| ------------------ | --------------------- | --------------------------------------- |
| `runeAddress`      | `string`              | Your Bitcoin address holding BTC UTXOs  |
| `runePublicKey`    | `string`              | X-only hex pubkey for your Arch account |
| `paymentAddress`   | `string \| null`      | (unused for BTC→Rune; pass `null`)      |
| `paymentPublicKey` | `string \| undefined` | (unused; pass `undefined`)              |
| `poolId`           | `string`              | Pubkey of the target pool               |
| `amountIn`         | `bigint`              | BTC to swap in (sats)                   |
| `amountOut`        | `bigint`              | Minimum Rune to receive                 |
| `feeRate`          | `bigint`              | Fee rate (sats per vbyte)               |

Example of usage:

```ts
const { tx, shardPubkeys } = await sdk.oneToZeroSwap.buildBtcToRunePsbt({
  runeAddress: wallet.address,
  runePublicKey: xOnlyHex,
  paymentAddress: null,
  paymentPublicKey: undefined,
  poolId: 'pool123…',
  amountIn: BigInt(1_000_000), // 0.01 BTC
  amountOut: BigInt(500), // 500 Rune
  feeRate: BigInt(10), // 10 sats/vbyte
});
```

#### Message Creation: `swapMessage`

Serializes the swap instruction, embedding your signed PSBT.

| Field            | Type             | Description                                    |
| ---------------- | ---------------- | ---------------------------------------------- |
| `runeAddress`    | `string`         | Your Bitcoin address (same as PSBT inputs)     |
| `runePublicKey`  | `string`         | X-only hex pubkey                              |
| `paymentAddress` | `string \| null` | (unused; pass `null`)                          |
| `randomNumber`   | `number`         | 1-byte nonce                                   |
| `feeRate`        | `bigint`         | Fee rate (sats per vbyte)                      |
| `poolId`         | `string`         | Pool pubkey                                    |
| `amountIn`       | `bigint`         | BTC in (sats)                                  |
| `amountOut`      | `bigint`         | Rune out                                       |
| `exactIn`        | `boolean`        | `true` = exact‐in swap                         |
| `signedPsbt`     | `string`         | Base64‐encoded signed PSBT                     |
| `shardPubkeys`   | `string[]`       | Shard pubkeys returned by `buildBtcToRunePsbt` |

Example of usage:

```ts
const { message, utxosInfo, splitRuneInputs } =
  await sdk.oneToZeroSwap.swapMessage({
    runeAddress: wallet.address,
    runePublicKey: xOnlyHex,
    paymentAddress: null,
    randomNumber: 0,
    feeRate: BigInt(10),
    poolId: 'pool123…',
    amountIn: BigInt(1_000_000),
    amountOut: BigInt(500),
    exactIn: true,
    signedPsbt: signedPsbtBase64,
    shardPubkeys, // from PSBT step
  });
```

### Swap Rune → BTC

Swap Rune tokens for BTC in a Saturn pool—exact‐in or exact‐out swap (you specify Rune in, receive ≥ BTC out).

- **Exact‐In Swap**: Trade `amountIn` Rune for at least `amountOut` BTC (sats).
- **Fee Handling**: Fees paid via BTC inputs in the final PSBT; split Rune PSBT may be needed to consolidate inputs.

#### PSBT Method: `buildRuneToBtcPsbt`

Builds the main swap PSBT, consuming a single Rune UTXO (possibly from split) and funding the BTC output.

| Field              | Type                  | Description                                                 |
| ------------------ | --------------------- | ----------------------------------------------------------- |
| `runeAddress`      | `string`              | Your Bitcoin address holding Rune UTXOs                     |
| `runePublicKey`    | `string`              | X-only hex public key                                       |
| `paymentAddress`   | `string \| null`      | (unused; pass `null`)                                       |
| `paymentPublicKey` | `string \| undefined` | (unused; pass `undefined`)                                  |
| `amountIn`         | `bigint`              | Rune to swap in                                             |
| `amountOut`        | `bigint`              | Minimum BTC to receive (sats)                               |
| `exactIn`          | `boolean`             | `true` = exact‐in swap                                      |
| `feeRate`          | `bigint`              | Fee rate (sats per vbyte)                                   |
| `poolId`           | `string`              | Pubkey of the target pool                                   |
| `splitRunePsbt`    | `string \| undefined` | Base64 split‐PSBT from `buildSplitRunePsbt`, or `undefined` |

Example of usage - may return splitUtxoTx:

```ts
let { tx: psbt, splitUtxoTx } = await sdk.zeroToOneSwap.buildRuneToBtcPsbt({
  runeAddress,
  runePublicKey,
  paymentAddress: null,
  paymentPublicKey: undefined,
  amountIn: BigInt(1_000), // 1,000 Rune
  amountOut: BigInt(50_000), // 50,000 sats
  exactIn: true,
  feeRate: BigInt(5),
  poolId: 'pool123…',
  splitRunePsbt: undefined,
});
```

#### PSBT Split Method: `buildSplitRunePsbt`

Splits Rune UTXOs to prepare a single‐UTXO input for the swap PSBT.

| Field              | Type                  | Description                               |
| ------------------ | --------------------- | ----------------------------------------- |
| `runeAddress`      | `string`              | Your Bitcoin address holding Rune UTXOs   |
| `runePublicKey`    | `string`              | X-only hex public key for signing         |
| `paymentWallet`    | `Wallet \| undefined` | (unused here; always `undefined`)         |
| `paymentPublicKey` | `string \| undefined` | (unused; pass same as `runePublicKey`)    |
| `collectionUtxos`  | `CollectionUtxo[]`    | Rune UTXOs from your wallet               |
| `collection`       | `Collection`          | Collection metadata for Rune              |
| `amountIn`         | `bigint`              | Rune amount to swap                       |
| `feeRate`          | `bigint`              | Fee rate (sats per vbyte)                 |
| `runeToBtcTxVsize` | `number`              | Estimated vsize of final swap transaction |

Example of usage - if splitUtxoTx present, sign & re-build:

```ts
if (splitUtxoTx) {
  const signedSplit = signer.signTransaction(
    wallet.address,
    splitUtxoTx.tx.psbt,
    splitUtxoTx.tx.inputs,
  );
  const splitBase64 = Buffer.from(signedSplit.toPSBT()).toString('base64');

  const result = await sdk.zeroToOneSwap.buildRuneToBtcPsbt({
    runeAddress,
    runePublicKey,
    paymentAddress: null,
    paymentPublicKey: undefined,
    amountIn: BigInt(1_000),
    amountOut: BigInt(50_000),
    exactIn: true,
    feeRate: BigInt(5),
    poolId: 'pool123…',
    splitRunePsbt: splitBase64,
  });
  psbt = result.tx;
  splitUtxoTx = result.splitUtxoTx;
}
```

#### Message Creation: `swapMessage`

Serializes the swap instruction, embedding your signed PSBT(s).

| Field            | Type             | Description                               |
| ---------------- | ---------------- | ----------------------------------------- |
| `runeAddress`    | `string`         | Your Bitcoin address (PSBT input owner)   |
| `runePublicKey`  | `string`         | X-only hex pubkey                         |
| `paymentAddress` | `string \| null` | (unused; pass `null`)                     |
| `randomNumber`   | `number`         | 1-byte nonce                              |
| `feeRate`        | `bigint`         | Fee rate (sats per vbyte)                 |
| `poolId`         | `string`         | Pool pubkey                               |
| `amountIn`       | `bigint`         | Rune in                                   |
| `amountOut`      | `bigint`         | BTC out (sats)                            |
| `exactIn`        | `boolean`        | `true` = exact‐in swap                    |
| `signedPsbt`     | `string`         | Base64‐encoded signed main PSBT           |
| `splitRunePsbt?` | `string`         | Base64‐encoded signed split PSBT (if any) |

Example of usage:

```ts
const message = await saturnSdk.zeroToOneSwap.swapMessage({
  runeAddress: wallet.address,
  runePublicKey: publicKeyHex,
  paymentAddress: null,
  amountIn: runeAmount,
  amountOut: btcAmount,
  exactIn: true,
  poolId,
  randomNumber: 0,
  feeRate,
  signedPsbt: Buffer.from(signedPsbt.toPSBT()).toString('base64'),
  splitRunePsbt: signedSplitRunePsbt
    ? Buffer.from(signedSplitRunePsbt.toPSBT()).toString('base64')
    : undefined,
});
```