> ## Documentation Index
> Fetch the complete documentation index at: https://build.flashnet.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Swaps

> Use TradingClient to trade through Flashnet markets

`TradingClient` wraps `ExecutionClient` and handles the full DEX path: pulling assets in from Spark, calling the Conductor swap, and sweeping the output back. One signed intent per swap on the EIP-2612 (`useAvailableBalance`) and Permit2 paths; two on the default `exact` approval path (one to approve, one to swap).

## Setup

```typescript theme={null}
import { ExecutionClient, TradingClient } from "@flashnet/sdk";

const execClient = new ExecutionClient(sparkWallet);
await execClient.authenticate();

const trading = new TradingClient(execClient, "regtest");
```

`"regtest"` loads the staging contract preset (Conductor, QuoterV2, factory, position manager, WBTC, Permit2), so swaps and `quote()` resolve without addresses. `ExecutionClient` picks up the matching gateway endpoints from the wallet's network.

For a custom deployment, pass explicit config instead: `new ExecutionClient(sparkWallet, { gatewayUrl, rpcUrl, chainId })` and a `TradingConfig` object. `TradingConfig` requires `conductorAddress`; `quote()` additionally needs `quoterV2Address` and `factoryAddress`, and throws if either is missing; optional fields are `permit2Address`, `approvalMode`, `swapGasLimit`, `approveGasLimit`. See [Approval modes](#approval-modes) below.

## Swap from Spark balance

The most common flow: input asset is sitting in the user's Spark wallet, output should land back in the same Spark wallet.

```typescript theme={null}
const result = await trading.swap({
  assetInAddress: "btc",
  assetOutAddress: "0x...",          // EVM address of the output token (Spark token contract)
  amountIn: "100000",                // 100,000 sats in
  minAmountOut: "950000000000000000",
  fee: 3000,                         // 0.3% Uniswap V3 tier
  withdraw: true,
  useAvailableBalance: true,         // pull amountIn from the Spark wallet
});

console.log(result.intentId);
console.log(result.evmTxHash);
console.log(result.inboundSparkTransferId);
```

`useAvailableBalance: true` makes the SDK send the Spark transfer to the gateway and bundle the resulting `transferId` into the same intent that runs the swap. Nothing is expected to sit on the EVM address before or after.

For ERC20 inputs, set `assetInSparkTokenId` to the Spark-side `tokenIdentifier` (bech32m form) corresponding to `assetInAddress` so the SDK can issue the matching Spark transfer. The token contract must implement EIP-2612 Permit (`SparkToken` does), so no prior on-chain `approve` is required. The output side never needs a token id. It's derived from the contract address.

## Swap from existing EVM balance

If the assets are already on the Execution side (deposited earlier or held from a previous swap), skip `useAvailableBalance`:

```typescript theme={null}
const result = await trading.swap({
  assetInAddress: "0x...",          // ERC20 contract on Execution
  assetOutAddress: "btc",
  amountIn: "1000000",
  minAmountOut: "95000",            // sats
  fee: 3000,
  withdraw: true,
});
```

For non-Permit tokens the SDK transparently submits a one-time approval intent before the swap. For Permit tokens the swap is one intent.

## Approval modes

The non-`useAvailableBalance` path needs to authorize the Conductor to pull input tokens. Configure on the `TradingClient`:

| `approvalMode`      | Behavior                                                                                                                                                                                                                                 |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"exact"` (default) | Submit a one-off `approve(conductor, amountIn)` intent before the swap, poll until it lands. Two intents per swap.                                                                                                                       |
| `"permit2"`         | Sign an EIP-712 `PermitTransferFrom` and pass it to `swap*WithPermit2`. One intent per swap. Requires `permit2Address` on `TradingConfig` (Uniswap canonical is `0x000000000022D473030F116dDEE9F6B43aC78BA3`; localnet deploys its own). |

The `useAvailableBalance` path uses EIP-2612 Permit on the SparkToken directly and bypasses both modes.

## Quoting

`trading.quote()` computes the quote client-side: it reads the Uniswap V3 `QuoterV2` and the Conductor fee getters over RPC, then returns the output net of the Conductor fee. It needs `quoterV2Address` and `factoryAddress` on the `TradingClient` and throws without them.

```typescript theme={null}
const quote = await trading.quote({
  assetInAddress: "btc",
  assetOutAddress: "0x...",
  amountIn: "100000",          // 100,000 sats
  fee: 3000,                   // 0.3% tier
  slippageBps: 50,             // 0.5%
  integratorBps: 25,           // optional, folded into the fee
});

quote.amountOut;          // expected output, net of the Conductor fee
quote.minAmountOut;       // amountOut after slippageBps; pass straight to swap()
quote.conductorFeeBps;    // total fee taken: protocol + host + integrator
quote.conductorFeeAmount; // fee amount, in conductorFeeAsset units
quote.conductorFeeAsset;  // address of the fee asset
```

Feed `quote.minAmountOut` into `swap()`. Passing `minAmountOut: "0"` disables slippage protection; don't ship that.

## Fee tiers

Match the tier of the pool you intend to trade against:

| Tier    | Use case                   |
| ------- | -------------------------- |
| `500`   | Stablecoin pairs           |
| `3000`  | Most pairs                 |
| `10000` | Long-tail / volatile pairs |

If the pool doesn't exist at the tier you pass, the swap reverts.

## Integrator fees

Pass `integratorFeeRateBps` and `integratorPublicKey` to take a basis-point cut of the swap that the Conductor routes to your Spark address.

```typescript theme={null}
await trading.swap({
  // ...
  integratorFeeRateBps: 25,        // 0.25%
  integratorPublicKey: "02ab...",
});
```

`integratorFeeRateBps` is an integer from 0 to 1000 (10% cap); the SDK rejects anything outside that range. It is one of three cuts the Conductor takes on each swap, alongside a protocol fee and a per-pool host fee. `quote()` returns their sum as `conductorFeeBps`, and the `amountOut` it reports is already net of all three. See the [Conductor reference](/products/execution/conductor#fees) for how the fee asset is chosen and claimed.

## What the result tells you

```typescript theme={null}
interface SwapResult {
  submissionId: string;          // gateway submission id (track admission)
  intentId: string;              // canonical intent id
  status: IntentStatus;          // "ACCEPTED" | "INCLUDED_PENDING_FINALITY" | "FINALIZED" | "EXPIRED"
  evmTxHash: string;             // keccak of the signed tx, for explorer linking
  inboundSparkTransferId?: string; // set when useAvailableBalance was true
}
```

When the intent reaches `FINALIZED`, the output asset has been dispatched back to your Spark wallet. Poll the gateway for status or subscribe to your Spark wallet's transfer events on the receiving side. See [Intents → Status lifecycle](/products/execution/intents#status-lifecycle) for the full lifecycle.

## Failure modes

The intent admits but the swap can still revert on-chain. The most common reasons:

* **Slippage exceeded.** Output below `minAmountOut`. Tighten or widen the bound and retry.
* **Pool missing.** Wrong `fee` tier, or the pool hasn't been created yet.
* **Insufficient input.** The deposit didn't fund what the swap expected. Recheck `amountIn` and Spark transfer amount.

The gateway returns the revert selector in the intent's `statusMessage`. Use `decodeRevertReason()` from `@flashnet/sdk` to translate it.

## Two-intent fallback

For tokens that don't implement Permit (and `approvalMode: "exact"`), the SDK splits the flow into one approval intent followed by the swap intent. You call `swap()` once and the SDK handles the sequencing. The tradeoff is the time it takes for the approval intent to finalize before the swap fires (typically sub-second on Flashnet sequencer policy, capped at a 30s SDK timeout).

## Cross-references

* [Conductor reference](/products/execution/conductor) for raw calldata if you need to call the contract directly.
* [Reading state](/products/execution/reading-state) for token info and balance helpers.
* [Spark tokens](/products/execution/spark-tokens) for how Spark assets map to EVM addresses.
