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
"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 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.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), skipuseAvailableBalance:
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). |
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.
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 |
Integrator fees
PassintegratorFeeRateBps and integratorPublicKey to take a basis-point cut of the swap that the Conductor routes to your Spark address.
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 for how the fee asset is chosen and claimed.
What the result tells you
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 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
feetier, or the pool hasn’t been created yet. - Insufficient input. The deposit didn’t fund what the swap expected. Recheck
amountInand Spark transfer amount.
statusMessage. Use decodeRevertReason() from @flashnet/sdk to translate it.
Two-intent fallback
For tokens that don’t implement Permit (andapprovalMode: "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 for raw calldata if you need to call the contract directly.
- Reading state for token info and balance helpers.
- Spark tokens for how Spark assets map to EVM addresses.