SwapCard

The swap composer over the V2 router — an honest getAmountsOut quote with its exact minimum received, and the approval and the swap kept as the two transactions they are.

import { SwapCard } from '@flarekit-dev/react-ui'

SwapCard is one currency context over a Uniswap V2 router — BlazeSwap on Coston2, SparkDEX's V2 router on Flare. The price is a getAmountsOut read, the floor is the quote's own minReceived, and the approval and the swap stay two transactions, because that is what they are.

Live#

The preview runs the gallery's own states. The switcher walks the states the card was verified against, each built from the real swap state machine, so nothing here shows a state the card never reaches.

mock kit

Swap

Coston2Ready

No limit-order venue is wired; Uniswap V2 has no native limit order.

You pay
Balance 247.500000
You receive
Rate1 FXRP = 1.224000 USD₮0
Minimum received121.788000 USD₮0
Price impact0.87%
Max slippage0.50%
RouteFXRP → USD₮0

Usage#

The card is prop-driven and reads nothing itself. You quote, you read the allowance, and the state machine turns that pair into the state the card renders — ready when the allowance covers the trade, awaiting_approval when it does not.

import { dexFor } from '@flarekit-dev/contracts'
import { applyQuote, createSwap, quoteSwap, readAllowance, startQuoting } from '@flarekit-dev/core'
import { SwapCard } from '@flarekit-dev/react-ui'
import '@flarekit-dev/react-ui/styles.css'

const dex = dexFor(114) // Coston2

export async function quoted(reader, intent, owner, now) {
  const { fromKey, toKey, amountIn, slippageBips } = intent
  const result = await quoteSwap({ reader, chainId: 114, fromKey, toKey, amountIn, slippageBips, now })
  const allowance = await readAllowance(reader, 114, fromKey, owner)
  const quoting = startQuoting(createSwap({ chainId: 114, intent, now }), now).record
  return { operation: applyQuote(quoting, { result, allowance, now }).record, result }
}

export function Swap({ operation, result }) {
  return (
    <SwapCard
      operation={operation}
      quoteResult={result}
      fromToken={dex.tokens.FXRP}
      toToken={dex.tokens.USDT0}
      networkLabel="Coston2"
      onSubmit={signNextStep}
    />
  )
}

Props#

PropTypeDefaultDescription
operationrequiredSwapOperationThe swap record. Its state drives the CTA, the note, the spine and whether the quote is still shown as live.
fromTokenrequiredDexTokenThe pay token. It comes from the network's dex registry in @flarekit-dev/contracts, never a literal.
toTokenrequiredDexTokenThe receive token, from the same registry.
quoteResultSwapQuoteResultThe latest reading — a quote, a no_route with its reason, or an unavailable read. Drives the receive amount and the detail rows.
priceImpactBipsnumber | nullPrice impact in bips, measured against a reference quote. Null or omitted renders an em dash, because an uncomputed impact is not zero impact.
amountInTextstringThe pay leg's shown text while the reader is typing. Omit it and the leg shows the intent's own amount.
fromBalanceAmountThe holder's balance of the pay token, when read. Absence is unknown, so the row is simply not shown.
toBalanceAmountThe holder's balance of the receive token, when read.
mockLabelstringA labelled mock kit's name, rendered as an explicit mock badge. Mock mode is declared on screen, never inferred.
networkLabelstringThe network the quote was read on, named in the header.
onAmountInChange(text: string) => voidCalled as the pay amount is typed. Omit it and the field is inert.
onFlip() => voidCalled when the reader switches the pay and receive sides.
onSelectFrom() => voidCalled when the pay token's picker is opened — this is where you mount a TokenSelector.
onSelectTo() => voidCalled when the receive token's picker is opened.
onMax() => voidCalled by the MAX affordance on the pay leg. The button appears only when this is given, so it never offers a fill the host cannot compute.
onSubmit() => voidCalled for the CTA — approve, review or re-quote, depending on state. The card signs nothing and broadcasts nothing itself.
theme'light' | 'dark'Overrides the inherited theme. Normally left unset — the widget follows data-theme.
classNamestringExtra class on the outer element, so a host layout can place the card.

What it renders#

One panel: the order tabs, a pay leg and a receive leg with the flip control between them, the note for the current state, and the quote details — rate, minimum received, price impact, max slippage and route. Once the swap is in flight the two steps render on the shared OperationTimeline spine, and each transaction hash appears as its own labelled evidence chip, so the approval is never folded into the swap.

States#

Every state in the switcher above is imported from packages/react-ui/gallery/, one source of truth for both the gallery and these docs:

  • quote — a real getAmountsOut reading, with the rate, the exact minReceived floor and the measured price impact on screen.
  • needs approval — the allowance is short of the trade, so the plan carries an approve step and the CTA names the token it will approve.
  • approving — the approval transaction is in flight; the spine shows it as the first of two steps.
  • swapping — the approval is done and its hash is on the card; the swap transaction is submitted and its outcome is not known yet.
  • successSwapped, with the approval and swap hashes as separate evidence. The receive amount reads : the exact fill is recorded on the swap transaction, and the pre-trade quote is never redressed as a receipt.
  • no route — no pool pairs these tokens, stated with the reason. The output is , never 0, and there is nothing to sign.
  • slippage exceeded — the pool moved past the minimum received before the swap confirmed. The revert is atomic, so nothing moved; the state is distinct and re-quotable, not a failure of the kit.
  • Limit — the tab is shown, disabled, and carries its reason: no limit-order venue is wired, and Uniswap V2 has no native limit order.

Mock to live#

The card never quotes; the reader does. createMockSwapReader() is a labelled SwapReader that the real quoteSwap and readAllowance run against, priced with the V2 constant-product curve over the actual Coston2 pool reserves — so moving to live swaps the reader, not the screen.

// From this…
const reader = createMockSwapReader({ allowance: 0n })

// …to this. The component does not change.
const reader = createPublicClient({ chain: coston2, transport: http() })

What it will not do#

It will not price a pair with no pool, and it will not show the quoted output as the amount received once the trade concludes. It will not collapse the approval into the swap, and it will not present a slippage revert as a failed swap — the revert is atomic, so it says so and offers a re-quote.