Skip to content

Conversation

@mikejhale
Copy link
Contributor

@mikejhale mikejhale commented Jan 6, 2026

Note

Introduces a self-contained swap demo under solana/jupiter-ultra-swap using Next.js, Jupiter Ultra API, QuickNode RPC, and Solana Wallet Adapter.

  • Server: Adds API routes api/tokens, api/balances, api/quote, api/execute, and api/rpc with API key support, address validation, error handling, timeouts, and BigInt precision for amounts
  • Client: Implements UI components (SwapCard, TokenInput, TokenSelector, SwapButton, StatusMessage, WalletButton) and hooks (useTokenList, useTokenBalances, useQuote, useSwap) to fetch quotes, sign transactions, and execute swaps
  • Wallet/RPC: Adds WalletProvider wired to a server-side RPC proxy and uses @solana/web3.js only for signing the Ultra transaction
  • Lib/Types: Adds lib/jupiter.ts, lib/types.ts, and a simple solana-client helper
  • Project setup: Adds Tailwind, Next config, TypeScript config, .env.example, .gitignore, and a README with setup instructions

Written by Cursor Bugbot for commit 123d65c. This will update automatically on new commits. Configure here.

@mikejhale mikejhale force-pushed the jupiter-ultra-swap-api branch from 9f353ba to 740621b Compare January 7, 2026 15:40
Comment on lines +36 to +39
const response = await fetch(`${JUPITER_ULTRA_BASE}/v1/holdings/${walletAddress}`, {
headers: getHeaders(),
cache: "no-store",
});

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI 4 days ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

Copy link

@mikemaccana mikemaccana Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mike I think you can resolve this due your isAddress()validation.

Copy link

@mikemaccana mikemaccana left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some easily fixable parseInt() issues (base 16 is a silly default] - but otherwise good to go! actually it looks like browsers have changed the default to base 10 if the radix is undefined now, there's some comments below but no blockers! 👍

if (data.amount && parseInt(data.amount) > 0) {
balances.push({
mint: "So11111111111111111111111111111111111111112", // SOL mint address
balance: parseInt(data.amount),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseInt is base16 by default, use Number() or parseInt(string, 10). Number is better though.

// If Jupiter API key is missing, fetch SOL balance via RPC as fallback
// This allows users to swap SOL even without the API key
if (!JUPITER_API_KEY) {
const balances: any[] = [];
Copy link

@mikemaccana mikemaccana Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Nitpick, feel free to ignore) for consistency with the rest of the code, I always use the generics syntax. Eg you have Promise<number> elsewhere, use Array<any> here.

AI can change this in one step!

} catch (error) {
console.error("Error executing swap:", error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : "Failed to execute swap" },

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the weird things about JS is that you can throw anything - an array, an object, a string, a number - but in reality, nobody ever does. So now everyone has to write error instanceof Error. This isn't useful advice just an old man grumbling. 😅


@layer utilities {
.text-balance {
text-wrap: balance;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oooh fancy 🎩🧐

tokensAreDifferent &&
hasAmount &&
!hasInsufficientBalance &&
status === "idle";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good variable naming here this is all super clear 👍

readOnly = false,
}: TokenInputProps) {
const handleMaxClick = () => {
if (balance !== undefined && balance > 0) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a dig deal, but you could do if balance here - null, undefined, and 0 would all be false.

Copy link

@mikemaccana mikemaccana left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved! Some hopefully comments below on optimisations but that's it.

Copy link
Contributor

@0xsergen 0xsergen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got the error below while trying a swap SOL -> USDC
image

@mikejhale mikejhale requested a review from 0xsergen January 12, 2026 21:20
@mikejhale mikejhale merged commit 26a13ae into main Jan 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants