Node + React Quickstart
Get up and running with Swaps in an instant with Node and React
Overview

In this Quickstart guide, we will implement core swap functionality using the Swaps API, but to a limited degree; only with the ability to swap SOL on Solana to ETH on Ethereum Mainnet. Two endpoints are all we need - Get Swap Quote and Execute a Swap.
We will be using Node on our backend and React on our frontend.
Whether you are a beginner or a seasoned expert, this guide will take you through all of the necessary steps to have you swapping cryptocurrency cross-chain in no time.
What you will accomplish
In this tutorial, you will:
- Configure a Swaps widget in your local environment
- Build a front end widget for Swaps using React
- Build a back end to power your front end widget using Node, and our API endpoints:
- Get swap quote
- Execute a swap
- Display the functional widget locally in your browser
Prerequisites
Before beginning, you will need:
- A Swaps.xyz account: If you do not have one, contact us.
- An IDE (coding software): Some popular ones are Visual Studio Code and IntelliJ WebStorm. We will be using Visual Studio Code in this tutorial.
- A web browser: Any web browser will work. We will be using Google Chrome in this tutorial.
Implementation
Step 1: Define a file structure
We will be working with a React frontend and a Node backend. This will require an organized file structure. Let's define one below:
swaps-node/
βββ swaps-fe/ β React frontend
β βββ src/
β βββ SwapWidget.jsx
β βββ App.jsx
βββ swaps-be/ β Node backend
β βββ index.js
β βββ package.json
Step 2: Set up your project structure
First, let's set up our swaps node
root project folder
mkdir swaps-node
cd swaps-node
Next, make sure you are in your swaps-node
root folder and let's set up the Node backend in a subfolder called swaps-be
first
mkdir swaps-be
cd swaps-be
npm init -y
npm install express cors axios
touch index.js
cd ..
Once that is done, let's create the frontend part of the project in a subfolder called swaps-fe
. Make sure you are back in your swaps-node
root folder and run
npm create vite@latest swaps-fe --template react
cd swaps-fe
npm install
cd ..
Now, we need to do some reorganization of the frontend files. Navigate to swaps-fe/src
and create an empty file named SwapWidget.jsx
.
Next, in swaps-fe/src
, find your App.jsx
folder and replace its contents with the following:
import React from "react";
import SwapWidget from "./SwapWidget";
import "./App.css";
function App() {
return (
<div className="container">
<SwapWidget />
</div>
);
}
export default App;
This will change your entrypoint to SwapWidget.jsx
, ensuring that Swaps is the only thing rendered upon running the app.
Once you are finished with Step 2, you should have a file structure that looks like this:

Step 4: Populate your backend index.js
file with code
index.js
file with codeOur Node backend will run on the following code:
const express = require("express");
const cors = require("cors");
const axios = require("axios");
const app = express();
app.use(cors());
app.use(express.json());
const SWAPS_API_BASE = "https://api.swaps.xyz/v1";
const HEADERS = {
accept: "application/json",
"content-type": "application/json",
"x-pk-key": "YOUR_PUBLIC KEY", // DO NOT HARDCODE IN PROD
"x-sk-key": "YOUR_SECRET_KEY" // USE .env INSTEAD
};
const REQUIRED_QUOTE_FIELDS = [
"fromCurrency",
"fromNetwork",
"fromWalletAddress",
"fromAmount",
"toCurrency",
"toNetwork",
"flow"
];
const REQUIRED_EXECUTE_FIELDS = [
"signature",
"refundWalletAddress",
"toWalletAddress"
];
app.post("/quote", async (req, res) => {
const data = req.body;
const missing = REQUIRED_QUOTE_FIELDS.filter(field => !(field in data));
if (missing.length > 0) {
return res.status(400).json({ error: `Missing required fields: ${missing.join(", ")}` });
}
console.log("Payload to Swaps.xyz:", data);
try {
const response = await axios.post(`${SWAPS_API_BASE}/swap/quote`, data, { headers: HEADERS });
res.status(response.status).json(response.data);
} catch (error) {
const status = error.response?.status || 500;
const message = error.response?.data || { error: error.message };
console.error("Swaps.xyz returned an error:", message);
res.status(status).json({ error: message });
}
});
app.post("/execute", async (req, res) => {
const data = req.body;
const missing = REQUIRED_EXECUTE_FIELDS.filter(field => !(field in data));
if (missing.length > 0) {
return res.status(400).json({ error: `Missing required fields: ${missing.join(", ")}` });
}
try {
const response = await axios.post(`${SWAPS_API_BASE}/swap/execute`, data, { headers: HEADERS });
res.status(response.status).json(response.data);
} catch (error) {
const status = error.response?.status || 500;
const message = error.response?.data || { error: error.message };
console.error("Swaps.xyz returned an error:", message);
res.status(status).json({ error: message });
}
});
const PORT = 5001;
app.listen(PORT, () => {
console.log(`Swaps Node backend running at http://localhost:${PORT}`);
});
It is important to note that every request needs x-pk-key
in the header; only Get Swap Quote and Execute a Swap need x-sk-key
as well.
This backend will:
- β Act as a lightweight proxy between your frontend and the Swaps API
- β Validate that all required parameters are present before making upstream requests with axios
- β Call Swaps.xyzβs /swap/quote endpoint to retrieve a real-time swap quote in response to select frontend changes
- β Call Swaps.xyzβs /swap/execute endpoint to initiate a swap using the quoteβs signature when the "Swap" button is clicked on the frontend
- β Forward Swaps responses (both quote and execution) directly to the frontend
- β Return error messages in JSON if anything fails
- β Log payloads and upstream responses for debugging/integration testing
- β Enable CORS support so that our React frontend can interact with it safely
Step 5: Populate your frontend SwapWidget.jsx
file with code
SwapWidget.jsx
file with codeOur React frontend will run on the following code:
import React, { useState, useEffect } from "react";
import axios from "axios";
export default function SwapWidget() {
const [formData, setFormData] = useState({
fromCurrency: "sol",
fromNetwork: "sol",
fromWalletAddress: "",
fromAmount: "",
toCurrency: "eth",
toNetwork: "eth",
flow: "standard",
});
const [quote, setQuote] = useState(null);
const [signature, setSignature] = useState(null);
const [toWalletAddress, setToWalletAddress] = useState("");
const [executionResult, setExecutionResult] = useState(null);
useEffect(() => {
if (formData.fromAmount && formData.fromWalletAddress) {
const fetchQuote = async () => {
try {
const res = await axios.post("http://localhost:5001/quote", formData);
setQuote(res.data);
setSignature(res.data.signature);
} catch (err) {
console.error("Failed to fetch quote:", err);
}
};
fetchQuote();
}
}, [formData.fromAmount, formData.fromWalletAddress]);
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleExecute = async () => {
try {
const res = await axios.post("http://localhost:5001/execute", {
signature,
refundWalletAddress: formData.fromWalletAddress,
toWalletAddress,
});
setExecutionResult(res.data);
} catch (err) {
console.error("Failed to execute swap:", err);
}
};
return (
<div className="widget">
<h1 style={{ fontSize: "1.5rem", fontWeight: 600, textAlign: "center", marginBottom: "2rem" }}>Swaps.xyz</h1>
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: "1rem" }}>
<div style={{ width: "320px" }}>
<input
name="fromWalletAddress"
placeholder="Your SOL Wallet Address"
onChange={handleInputChange}
style={{ width: "100%", padding: "0.625rem", borderRadius: "0.375rem", border: "1px solid #e5e7eb", textAlign: "center", marginBottom: "1rem" }}
/>
<input
name="fromAmount"
placeholder="Amount of SOL to swap"
onChange={handleInputChange}
style={{ width: "100%", padding: "0.625rem", borderRadius: "0.375rem", border: "1px solid #e5e7eb", textAlign: "center", marginBottom: "1rem" }}
/>
<div style={{ color: "#6b7280", textAlign: "center", padding: "0.5rem 0", marginBottom: "1rem" }}>β Swap to β</div>
<input
value={formData.toCurrency.toUpperCase() + " on " + formData.toNetwork.toUpperCase()}
disabled
style={{ width: "100%", padding: "0.625rem", borderRadius: "0.375rem", border: "1px solid #e5e7eb", textAlign: "center", marginBottom: "1rem", backgroundColor: "#f3f4f6", fontWeight: 500 }}
/>
{quote && (
<p style={{ fontSize: "0.875rem", textAlign: "center", color: "#047857", marginBottom: "1rem" }}>
You will receive: <strong>{quote.toAmount} {quote.toCurrency.toUpperCase()}</strong>
</p>
)}
<input
placeholder="Your ETH Wallet Address"
value={toWalletAddress}
onChange={(e) => setToWalletAddress(e.target.value)}
style={{ width: "100%", padding: "0.625rem", borderRadius: "0.375rem", border: "1px solid #e5e7eb", textAlign: "center", marginBottom: "1rem" }}
/>
<div style={{ display: "flex", justifyContent: "center", width: "100%" }}>
<button
onClick={handleExecute}
style={{ padding: "0.625rem 2rem", borderRadius: "0.375rem", backgroundColor: "#2563eb", color: "white", border: "none", cursor: "pointer" }}
>
Swap Now
</button>
</div>
{executionResult && (
<div style={{ marginTop: "1rem", padding: "0.75rem", border: "1px solid #e5e7eb", borderRadius: "0.375rem", backgroundColor: "#f0fdf4", fontSize: "0.875rem", textAlign: "center" }}>
<p>Status: <strong>{executionResult.status}</strong></p>
<p>Send your SOL to:</p>
<p style={{ fontFamily: "monospace", fontSize: "0.75rem", wordBreak: "break-all", color: "#1f2937" }}>{executionResult.depositAddress}</p>
</div>
)}
</div>
</div>
</div>
);
}
Aside from some work to make the page look a little bit better, this frontend will do a few things:
- β Collect the userβs origin wallet address and amount for the swap
- β Dynamically fetch a swap quote from your Node backend (which calls Swaps.xyz) whenever the amount or wallet changes
- β Display the real-time quote showing how much of the destination asset will be received (using standard rates)
- β Collect the destination wallet address (i.e. ETH wallet for the user on another chain)
- β Trigger the swap execution via your backend by passing the signed quote and destination address
- β Display the deposit address where the user must send their funds to complete the swap
- β Handle basic loading and error cases in the console for debugging
Step 6: Run your backend and frontend on localhost
Now that you have your code, it's time to run these files on localhost. We will be running our backend on Port 5001
and our frontend on Port 5173
.
Running the backend
- Navigate to your backend folder
swaps-be
cd swaps-be
- Start
index.js
onPort 5001
(already specified in code) using this command
node index.js
Running the frontend
- Open a separate terminal tab/window
- Navigate to your root directory (
swaps-node/
) and do
cd swaps-fe/src
- Next, use
npm
to run the frontend onPort 5173
npm run dev
Note: you may need to install Node to get npm
working, if you haven't already. You can download that from the NodeJS website right here.
Step 7: Testing the flow
Note: Swaps.xyz currently only supports production transactions, which means you will need to use real funds to test your flow.
Let's test our simple Swaps integration. First, navigate to your frontend at http://localhost:5173/
You should see our modest Swaps tool, looking something like this:

When we add our wallet address and some amount in SOL, we should see the UI update to reflect the amount we would receive in Mainnet ETH:

Let's add our Ethereum wallet address and click the "Swap Now" button. Once we do that, we should see this screen, which instructs us to send SOL to the deposit wallet address (response from the Execute endpoint):

Let's send that SOL over to the deposit wallet address now:

Optional: While you wait, you may track your transaction by navigating to developer tools > Network > your Execute request > Response > transaction ID. You can copy that ID and hit the Get Swap Transaction with Status endpoint to track your transaction. On a full UX, this would be integrated into the frontend (see Best Practices section next).
Once finished, you should see an updated balance in your Ethereum wallet!

Completion
Congratulations, you have just integrated the Swaps.xyz API using a Node backend and React frontend! π
There are so many ways to integrate this set of APIs and create a fantastic crypto swapping experience for your users. If you want learn how to make the integration even better, have a look at our Best Practices section.
Any questions? Feel free to visit our Help Center to dig into our other resources, or drop us a line!
Updated about 1 month ago
Now that you have a baseline integration, let's supercharge it by applying some Best Practices