Solana's high throughput and low transaction costs have made it a leading platform for decentralized applications, particularly in decentralized finance (DeFi). Among the most crucial DeFi functionalities is the ability to swap tokens efficiently. This comprehensive guide will walk you through building a robust backend API using Node.js that facilitates token swaps on the Solana blockchain by leveraging the Jupiter aggregation protocol.
You will learn how to set up a project, integrate with Solana's Web3 library, interact with Jupiter's API for quotes and swap execution, and structure your application for scalability. By the end of this tutorial, you will have a production-ready API endpoint capable of constructing token swap transactions.
Prerequisites and Initial Setup
Before diving into the code, ensure you have the following installed on your system:
- Node.js (version 18 or higher)
- Yarn or npm package manager
- A basic understanding of JavaScript and Node.js
- Familiarity with REST APIs and environment variables
Step 1: Initialize Your Node.js Project
Begin by creating a new project directory and initializing a Node.js project using Yarn. Open your terminal and execute the following commands:
mkdir jupiter-swap-api
cd jupiter-swap-api
yarn init -y
This sequence creates a new folder and generates a package.json
file to manage your project's dependencies and scripts.
Step 2: Install Required Dependencies
Your API will rely on several key packages to function. Install them using Yarn:
yarn add @solana/web3.js @solana/spl-token @thewebchimp/primate dotenv
@solana/web3.js
: The official Solana JavaScript SDK for interacting with the blockchain.@solana/spl-token
: A library for creating and managing SPL tokens (the token standard on Solana).@thewebchimp/primate
: A lightweight and fast web framework for building APIs.dotenv
: A zero-dependency module that loads environment variables from a.env
file.
Step 3: Define the Project Structure
A well-organized project structure is vital for maintainability and future expansion. Create the following folders and files:
jupiter-swap-api/
│
├── app.js
├── controllers/
│ └── solana.controller.js
├── routes/
│ ├── default.js
│ └── solana.js
├── services/
│ └── solana.service.js
├── .env
├── package.json
└── test.http
This structure separates concerns: controllers handle request logic, services contain business logic, and routes manage API endpoints.
Developing the Core API Functionality
Step 4: Configure Environment Variables
Sensitive configuration details, such as RPC endpoints, should be stored in environment variables. Create a .env
file in your project's root directory and add the following:
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
JUPITER_QUOTE_API_URL=https://quote-api.jup.ag/v1/quote
JUPITER_SWAP_API_URL=https://swap-api.jup.ag/v1/swap
These variables point to the Solana mainnet and the Jupiter API endpoints for fetching quotes and executing swaps.
Step 5: Create the Application Entry Point
The app.js
file serves as the starting point for your application. It initializes the server and registers your route handlers.
import primate from '@thewebchimp/primate';
import { router as solanaRouter } from './routes/solana.js';
import { router as defaultRouter } from './routes/default.js';
primate.setup();
await primate.start();
primate.app.use('/solana', solanaRouter);
primate.app.use('/', defaultRouter);
This code imports the Primate framework, sets up the server, and mounts the routers. The /solana
path will handle all swap-related requests, while the root path provides a basic health check.
Step 6: Build the Solana Service Layer
The service layer (services/solana.service.js
) contains the core logic for interacting with the Solana blockchain and Jupiter. It is responsible for validating inputs, fetching mint data, getting quotes, and constructing the swap transaction.
import { Connection, PublicKey } from '@solana/web3.js';
import { getMint } from '@solana/spl-token';
import 'dotenv/config';
const connection = new Connection(process.env.SOLANA_RPC_URL, 'confirmed');
class SolanaService {
static async buildSwapTransaction(publicKey, inputMint, outputMint, amount, slippageBps = 0.5) {
try {
// Validate token mints exist on-chain
const inputMintData = await getMint(connection, new PublicKey(inputMint));
const outputMintData = await getMint(connection, new PublicKey(outputMint));
if (!inputMintData || !outputMintData) {
throw new Error('Invalid input or output mint');
}
// Validate slippage is within acceptable bounds
if (slippageBps > 10) {
throw new Error('Slippage must be less than 10%');
}
// Convert user-friendly amount to raw token amount using decimals
const inputDecimals = inputMintData.decimals;
const inputAmount = amount * Math.pow(10, inputDecimals);
const slippagePercentage = slippageBps * 100;
const walletPublicKey = new PublicKey(publicKey);
// Construct the URL to fetch a quote from Jupiter
let urlGet = `${process.env.JUPITER_QUOTE_API_URL}`;
urlGet += `?inputMint=${inputMint}&outputMint=${outputMint}`;
urlGet += `&amount=${inputAmount}&slippageBps=${slippagePercentage}`;
urlGet += `&swapMode=ExactIn`;
const quoteResponseData = await fetch(urlGet);
const quoteResponse = await quoteResponseData.json();
// Call Jupiter Swap API to construct the transaction
const response = await fetch(`${process.env.JUPITER_SWAP_API_URL}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
dynamicComputeUnitLimit: true,
prioritizationFeeLamports: 'auto',
quoteResponse,
userPublicKey: walletPublicKey.toString(),
wrapAndUnwrapSol: true,
}),
});
const jsonResponse = await response.json();
if (jsonResponse?.error || !jsonResponse?.swapTransaction) {
throw new Error(jsonResponse.error);
}
return jsonResponse.swapTransaction;
} catch (error) {
console.error('Error building swap transaction:', error);
throw new Error(error.message);
}
}
}
export default SolanaService;
Step 7: Implement the API Controller
The controller (controllers/solana.controller.js
) acts as the intermediary between the HTTP request and the service layer. It handles input validation and response formatting.
import SolanaService from '../services/solana.service.js';
class SolanaController {
static async swapTokens(req, res) {
const { inputMint, outputMint, amount, publicKey } = req.body;
let { slippage } = req.body;
// Validate all required parameters are present
if (!inputMint || !outputMint || !amount || !publicKey) {
return res.respond({
status: 400,
message: `Missing parameter: ${!inputMint ? 'inputMint' : !outputMint ? 'outputMint' : !amount ? 'amount' : 'publicKey'}`,
});
}
// Set a default slippage of 0.5% if not provided
if (!slippage) {
slippage = 0.5;
}
try {
// Delegate transaction building to the service layer
const transaction = await SolanaService.buildSwapTransaction(publicKey, inputMint, outputMint, amount, slippage);
res.respond({
status: 200,
data: { transaction },
message: 'Swap transaction created successfully',
});
} catch (error) {
// Handle errors gracefully
res.respond({
status: 500,
message: error.message,
});
}
}
}
export default SolanaController;
Step 8: Define the API Routes
Routes map incoming HTTP requests to the appropriate controller functions. Create two route files:
routes/solana.js
for the swap functionality:
import { getRouter } from '@thewebchimp/primate';
import SolanaController from '../controllers/solana.controller.js';
const router = getRouter();
router.post('/swap', SolanaController.swapTokens);
export { router };
routes/default.js
for a basic health check endpoint:
import { getRouter } from '@thewebchimp/primate';
const router = getRouter();
router.get('/', (req, res) => {
res.respond({
data: {
message: 'Jupiter Swap API is operational!',
},
});
});
export { router };
Testing and Deployment
Step 9: Test Your API Endpoint
You can test your /solana/swap
endpoint using an HTTP client like Postman or Thunder Client (VS Code extension). Create a test.http
file to quickly send a POST request with the necessary JSON body.
POST http://localhost:1337/solana/swap
Content-Type: application/json
{
"publicKey": "Your_Solana_Wallet_PublicKey",
"inputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC mint
"outputMint": "So11111111111111111111111111111111111111112", // SOL mint
"amount": 1,
"slippage": 0.5
}
A successful response will return a serialized transaction that can be signed and broadcasted to the network by a client-side wallet.
👉 Explore more strategies for transaction handling
Best Practices and Advanced Considerations
- Error Handling: Implement more granular error handling to distinguish between network errors, Jupiter API errors, and user input errors.
- Logging: Integrate a logging library like Winston to keep track of API requests and errors for debugging.
- Rate Limiting: Add rate limiting to your API endpoints to prevent abuse and ensure service availability.
- Security: Validate and sanitize all user inputs thoroughly to prevent injection attacks or unexpected behavior.
- Caching: Consider caching Jupiter quote responses for a very short period for identical requests to improve response times and reduce API calls.
Frequently Asked Questions
What is the Jupiter Aggregator?
Jupiter is a liquidity aggregator on the Solana blockchain. It finds the best possible swap routes across all major decentralized exchanges (DEXs) within the ecosystem, ensuring users get the optimal price for their token swaps.
Why do I need to convert the token amount using decimals?
Blockchains handle tokens in their smallest unit (e.g., lamports for SOL, or base units for SPL tokens). Converting a user-friendly amount (e.g., 1.5 USDC) to its base unit (1500000 for USDC, which has 6 decimals) ensures the transaction is created with the precise value the blockchain expects.
What is slippage and why is it important?
Slippage is the difference between the expected price of a trade and the price at which the trade actually executes. In volatile markets, prices change rapidly. Slippage tolerance protects users from executing trades at unexpectedly bad prices. Setting it too low may cause transactions to fail, while setting it too high increases the risk of a unfavorable trade.
Can this API be used for a production application?
The code in this guide provides a solid foundation. For production use, you must enhance it with robust error handling, logging, monitoring, rate limiting, and thorough security audits. Always use dedicated private RPC services like QuickNode or Helius for better performance and reliability instead of the public mainnet endpoint.
Does this API execute the swap?
No. This API constructs the swap transaction. The serialized transaction it returns must be signed by the user's private key (this should never happen on the backend) and then broadcasted to the network. The signing step is always performed securely on the client-side, typically by a wallet like Phantom or Solflare.
How can I get a list of available token mints?
You can fetch a list of all SPL token mints from Solana ecosystem repositories or directly from Jupiter's API. Jupiter often provides an endpoint for fetching a list of tokens it supports for trading. 👉 Get advanced methods for token discovery
Conclusion
You have successfully built a Node.js API backend that interfaces with the Solana blockchain and Jupiter to create token swap transactions. This guide covered project initialization, dependency management, service layer logic, controller input handling, and route definition. This API serves as a powerful backend for any decentralized application requiring token swap functionality, from simple trading tools to complex DeFi platforms.
The principles learned here—interacting with blockchain RPCs, managing async operations, and structuring a scalable backend—are applicable to a wide range of Web3 development projects. Continue to experiment, add features, and integrate this API with a frontend to create a full-stack dApp.