React Integration
The LuminaDex SDK provides first-class support for React applications through dedicated hooks and utilities. This guide explains how to integrate the SDK with your React application.
INFO
For a complete example, refer to the sdk-test-react project in the Lumina DEX monorepo.
Setting Up the Context Provider
TIP
Please refer to xstate documentation for detailled information about the available hooks.
The recommended approach is to use React Context to make the Wallet and DEX actors available throughout your application:
import {
createDex,
createWallet,
type LuminaContext as LC
} from "@lumina-dex/sdk"
import { createContext, ReactNode } from "react"
const Wallet = createWallet()
const Dex = createDex({
input: {
wallet: Wallet,
frontendFee: {
destination: "B62qmdQRb8FKaKA7cwaujmuTBbpp5NXTJFQqL1X9ya5nkvHSuWsiQ1H",
amount: 1 // 1% fee
}
}
})
// This is necessary for type-safety
const Context: LC = { Dex, Wallet }
export const LuminaContext = createContext(Context)
// Start-up your app
const rootElement = document.getElementById("app")
if (!rootElement?.innerHTML) {
const root = ReactDOM.createRoot(rootElement as HTMLElement)
root.render(
<LuminaContext.Provider value={Context}>
{/* Your app goes here */}
</LuminaContext.Provider>
)
}
Using the SDK in Components
The SDK re-exports xstate hooks : Use useSelector
to efficiently accessing state from the machines:
// src/components/WalletConnect.tsx
import { useSelector } from "@lumina-dex/sdk/react"
import { useContext, useEffect } from "react"
import { LuminaContext } from "../providers/LuminaProvider"
export function WalletConnect() {
// Get the wallet actor from context
const { Wallet } = useContext(LuminaContext)
// Use the useSelector hook to access state
const walletState = useSelector(Wallet, (state) => state.value)
const isReady = useSelector(Wallet, (state) => state.matches("READY"))
const account = useSelector(Wallet, (state) => state.context.account)
const minaBalance = useSelector(
Wallet,
(state) => state.context.balances["mina:devnet"]?.MINA || 0
)
// Handle connect button click
const connect = () => Wallet.send({ type: "Connect" })
// Automatically connect on component mount
useEffect(() => {
if (walletState === "INIT") {
connect()
}
}, [walletState])
return (
<div>
{!isReady ? <button onClick={connect}>Connect Wallet</button> : (
<div>
<p>Connected: {account}</p>
<p>MINA Balance: {minaBalance}</p>
</div>
)}
</div>
)
}
Fetching Token Data
You can fetch token data and update balances as follows:
import {
fetchPoolTokenList,
type Networks,
type TokenDbToken
} from "@lumina-dex/sdk"
import { useContext, useEffect, useState } from "react"
import { LuminaContext } from "../providers/LuminaProvider"
export function TokenList() {
const { Wallet } = useContext(LuminaContext)
const [tokens, setTokens] = useState<TokenDbToken[]>([])
const [loading, setLoading] = useState(false)
const fetchTokens = async () => {
setLoading(true)
try {
// Fetch token list from CDN
const result = await fetchPoolTokenList("mina:devnet")
setTokens(result.tokens)
// Update token balances in wallet
Wallet.send({
type: "FetchBalance",
network: "mina:devnet",
tokens: result.tokens.map((token) => ({
address: token.address,
decimal: 10 ** token.decimals,
tokenId: token.tokenId,
symbol: token.symbol
}))
})
} catch (error) {
console.error("Failed to fetch tokens:", error)
} finally {
setLoading(false)
}
}
// Fetch tokens when wallet is ready
const [loaded, setLoaded] = useState(false)
useEffect(() => {
if (loaded) return
Wallet.send({ type: "Connect" })
const end = Wallet.subscribe(() => {
if (loaded === false) {
setLoaded(true)
console.log("Wallet Ready")
fetchTokens()
end.unsubscribe()
}
})
}, [Wallet, loaded, fetchTokens])
return (
<div>
<h2>Available Tokens</h2>
{loading ? <p>Loading tokens...</p> : (
<ul>
{tokens.map((token) => (
<li key={token.tokenId}>
{token.symbol} - {token.address}
</li>
))}
</ul>
)}
</div>
)
}
Implementing Token Swapping
Here's an example of how to implement a basic token swap component:
import { canDoDexAction } from "@lumina-dex/sdk"
import { useSelector } from "@lumina-dex/sdk/react"
import { useContext, useState } from "react"
import { LuminaContext } from "../providers/LuminaProvider"
export function SwapComponent() {
const { Dex } = useContext(LuminaContext)
// Form state
const [pool, setPool] = useState(
"B62qjGnANmDdJoBhWCQpbN2v3V4CBb5u1VJSCqCVZbpS5uDs7aZ7TCH"
)
const [fromAddress, setFromAddress] = useState("MINA")
const [toAddress, setToAddress] = useState(
"B62qjDaZ2wDLkFpt7a7eJme6SAJDuc3R3A2j2DRw7VMmJAFahut7e8w"
)
const [amount, setAmount] = useState("1")
const [slippage, setSlippage] = useState(0.5)
// Get current state
const dexState = useSelector(Dex, (state) => state.value)
const swapSettings = useSelector(Dex, (state) => state.context.dex.swap)
const canDo = useSelector(Dex, (state) => canDoDexAction(state.context))
// Handle calculate swap
const calculateSwap = () => {
Dex.send({
type: "ChangeSwapSettings",
settings: {
pool,
from: {
address: fromAddress,
amount
},
to: toAddress,
slippagePercent: slippage
}
})
}
// Handle execute swap
const executeSwap = () => {
Dex.send({ type: "Swap" })
}
return (
<div>
<h2>Swap Tokens</h2>
<div>
<input
value={pool}
onChange={(e) => setPool(e.target.value)}
placeholder="Pool Address"
/>
</div>
<div>
<input
value={fromAddress}
onChange={(e) => setFromAddress(e.target.value)}
placeholder="From Token Address (or MINA)"
/>
</div>
<div>
<input
value={toAddress}
onChange={(e) => setToAddress(e.target.value)}
placeholder="To Token Address"
/>
</div>
<div>
<input
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount"
type="text"
/>
</div>
<div>
<input
value={slippage}
onChange={(e) => setSlippage(Number(e.target.value))}
placeholder="Slippage (%)"
type="number"
min="0.1"
max="10"
step="0.1"
/>
</div>
<button
onClick={calculateSwap}
disabled={!canDo.changeSwapSettings}
>
Calculate Swap
</button>
{swapSettings.calculated && (
<div>
<p>Expected output: {swapSettings.calculated.amountOut / 1e9}</p>
<button
onClick={executeSwap}
disabled={!canDo.swap}
>
Execute Swap
</button>
</div>
)}
</div>
)
}
Caveats
There's a few important caveats to mention. If you are using vite, you must configure the dependencies optimizer to play nicely with web workers :
export default defineConfig({
optimizeDeps: {
include: ["@lumina-dex/sdk > react", "@lumina-dex/sdk > @xstate/react"],
exclude: ["@lumina-dex/sdk"]
}
})
Your devserver/production server MUST use the following headers to use o1js :
const webWorkerHeaders = {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Resource-Policy": "same-site",
"Cross-Origin-Embedder-Policy": "require-corp"
}