Solana DEX Server
The Primodium DEX Server is a TypeScript-based tRPC server for Solana, providing protected API endpoints for building and sponsoring user transactions.
This package is available as a npm package at
@primodiumxyz/dex-server
(opens in a new tab),
and the Docker image is available at
ghcr.io/primodiumxyz/dex-server
(opens in a new tab).
The source code for this repository is available on Github
here (opens in a new tab).
Description
The Server offers a set of tRPC endpoints for various operations centered around building user transactions and sponsoring the SOL required for chain execution fees. It uses Fastify as the underlying web server.
The server provides comprehensive token trading functionality including real-time price tracking, automated fee calculations, and transaction sponsorship. It features WebSocket-based streaming for live price updates and iOS push notifications for price tracking. The system is built with configurability in mind, using Redis for live configuration updates and supporting automated background tasks for maintenance operations.
For price tracking to be enabled, the HASURA_URL
and HASURA_ADMIN_SECRET
environment variables must be set, and point to a running instance of the
DEX GraphQL package (opens in a new tab).
If you would like to use this feature,
you will need to run the DEX Indexer stack (opens in a new tab).
Features
- Transaction building and sponsorship
- Fee calculation and management system
- Solana wallet integration and balance tracking
- JWT-based authentication
- tRPC-based API for type-safe client-server communication
- WebSocket support for real-time updates
- Redis-based configuration management with live updates
- Automated background tasks via CronService
- Real-time token price tracking
- Apple Push Notification Service (APNS) integration for iOS live activities
Usage
To run the server:
pnpm start
Example client
const server = createServerClient({
httpUrl: "http://localhost:8888/trpc",
wsUrl: "ws://localhost:8888/trpc",
httpHeaders: () => {
const jwtToken = useUserStore.getState().jwtToken;
return {
Authorization: `Bearer ${jwtToken}`,
};
},
});
const results = await server.registerNewUser.mutate({
username: "test",
airdropAmount: "100",
});
Configuration
The server can be configured with the following environment variables:
Variable | Description | Default |
---|---|---|
NODE_ENV | Environment (local, dev, test, prod) | local |
SERVER_HOST | Host that the server listens on | 0.0.0.0 |
SERVER_PORT | Port that the server listens on | 8888 |
REDIS_HOST | Host that the Redis server listens on | localhost |
REDIS_PORT | Port that the Redis server listens on | 6379 |
REDIS_PASSWORD | Password for the Redis server | |
QUICKNODE_ENDPOINT | URL of the Quicknode endpoint | |
QUICKNODE_TOKEN | Token for the Quicknode endpoint | |
JUPITER_URL | Endpoint for the Jupiter V6 Swap API | |
HASURA_URL | URL of the Hasura endpoint | |
HASURA_ADMIN_SECRET | Admin secret for the Hasura endpoint | |
JWT_SECRET | Secret for JWT signing | secret |
PRIVY_APP_SECRET | Secret for Privy app | |
PRIVY_APP_ID | ID for Privy app | |
FEE_PAYER_PRIVATE_KEY | Private key for the fee payer | |
TEST_USER_PRIVATE_KEY | Private key for the test user | |
APPLE_PUSH_KEY_ID | Key ID for Apple Push Notifications | |
APPLE_PUSH_TEAM_ID | Team ID for Apple Push Notifications | |
APPLE_AUTHKEY | Auth key for Apple Push Notifications |
The server can be further configured with the following Redis variables in
default-redis-config.json
. Ensure that TRADE_FEE_RECIPIENT
is set to the
address of the account that will receive the trade fees.
Development
To set up the project for development:
-
Ensure all server-related env variables are set.
-
If Redis is not installed, make sure that
NODE_ENV
is set tolocal
in the root.env
file for Redis to be installed in theprepare
step ofpnpm install
. Refer toprepare
script in./package.json
(opens in a new tab) for details. -
Install dependencies:
pnpm install
-
To run this application in a standalone environment with Redis, run the following which starts both
redis-server
and theserver
application.
pnpm dev:standalone
-
For testing:
pnpm test
API Endpoints
The server exposes the following tRPC endpoints:
Query Procedures
-
getStatus
- Description: Returns the current status of the server
- Response:
{ status: number }
-
getSolUsdPrice
- Description: Returns the current SOL/USD price
- Response:
number
-
getSolBalance
- Description: Gets user's SOL balance
- Response:
number
-
getAllTokenBalances
- Description: Gets all token balances for user
- Response: Array of token balances
-
getTokenBalance
- Description: Gets balance for specific token
- Input:
{ tokenMint: string }
-
fetchSwap
- Description: Fetches a constructed swap transaction for the user. This
transaction will need to be signed by the user, then sent to the server via
submitSignedTransaction
. - Input:
{ buyTokenId: string, sellTokenId: string, sellQuantity: number, slippageBps?: number }
- Description: Fetches a constructed swap transaction for the user. This
transaction will need to be signed by the user, then sent to the server via
-
fetchPresignedSwap
- Description: Fetches swap transaction pre-signed by the server's fee payer. This transaction will need to be signed by the user but can be submitted to any Solana node.
- Input:
{ buyTokenId: string, sellTokenId: string, sellQuantity: number }
-
getEstimatedTransferFee
- Description: Gets estimated fee for transferring USDC to a different address
- Response: Fee estimate in USDC base units
-
fetchTransferTx
- Description: Fetches a constructed transfer transaction for the user. This
transaction will need to be signed by the user, then sent to the server via
submitSignedTransaction
. - Input:
{ toAddress: string, amount: string, tokenId: string }
- Description: Fetches a constructed transfer transaction for the user. This
transaction will need to be signed by the user, then sent to the server via
Subscription Procedures
-
subscribeSolPrice
- Description: Real-time SOL price updates
- Response: Stream of price updates
-
swapStream
[deprecated]- Description: Real-time swap quote updates. Currently deprecated and unused, but could be used in the future for real-time updates.
- Input:
{ request: { buyTokenId: string, sellTokenId: string, sellQuantity: number } }
Mutation Procedures
-
submitSignedTransaction
- Description: Submits a signed transaction to the server. This transaction will be sponsored by the server's fee payer and submitted to the Solana network.
- Input:
{ signature: string, base64Transaction: string }
-
updateSwapRequest
[deprecated]- Description: Updates parameters for an existing swap stream's request
- Input:
{ buyTokenId: string, sellTokenId: string, sellQuantity: number }
-
stopSwapStream
[deprecated]- Description: Stops an active swap stream
- Response: void
-
startLiveActivity
- Description: Starts live price tracking for a token
- Input:
{ tokenMint: string, tokenPriceUsd: string, deviceToken: string, pushToken: string }
-
stopLiveActivity
- Description: Stops live price tracking
- Response:
{ success: boolean }
Testing
Before running tests on the server, first create a .env.test
file with the
appropriate environment variables. See example.env.test
for an example.
-
Run the following to start the server:
pnpm dev:standalone
-
Then run the following command to start tests:
pnpm test
Testing Transactions Setup
You can test transactions by running the service.test.ts
file.
-
You may need to manually remove any
.skip
flags from the tests you want to run. These are placed there to prevent the tests from being run on every commit. -
Ensure that your
FEE_PAYER
has a few dollars worth of SOL in it to pay for the chain fees. If this is not met, the test transactions will fail. -
Ensure that your
FEE_PAYER
has an existing USDC ATA that has a rent-exempt balance (currently 0.002039 SOL). If this is not met, the test transactions will fail. -
Optionally, you can change the token being traded in the tests by editing
MEMECOIN_MAINNET_PUBLIC_KEY
insrc/constants/tokens.ts
. -
Check that the dev server is still running, then run the test file with the following command:
pnpm test service.test.ts