Payment API

Convert any token to USDC

Jupiter Metropolis API supports a variety of payment use cases. Leverage Jupiter + SolanaPay to pay for anything using any SPL token. With the Metropolis API, you can specify an exact output token amount. The API supports not only conversions to USDC but to any SPL token!

Use Case

Payments or interactions with a protocol may require an exact amount of token B. Users might not have token A or may prefer to hold other tokens long-term. The Jupiter Metropolis API allows you to build a swap transaction to receive an exact amount of token A for a maximum in amount of token B.

Practical Example

Bob is selling a delicious latte for 5 USDC. Alice wants to buy Bob's latte, but she only holds mSOL. Fortunately, Bob can use the Jupiter Payments API to let Alice swap for exactly 5 USDC and then transfer the 5 USDC to his payment wallet.

First, we need to show Alice how much mSOL she will need to spend for the latte. To do this, we use the GET /quote endpoint.

1. Get Quote

Retrieve a quote for swapping a specific amount of tokens.

curl -s 'https://api.jup.ag/v6/quote?inputMint=mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&amount=5000000&swapMode=ExactOut&slippageBps=50' | jq '.inAmount, .otherAmountThreshold'

📘

ExactOut mode

Currently only Orca Whirlpool, Raydium CLMM, and Raydium CPMM support ExactOut mode. All token pairs may not be available in this mode. To see more price options use ExactIn mode.

Then, Bob creates the transaction using the POST /swap endpoint and adds a 5 USDC token transfer from Alice to his payment wallet by specifying the destinationTokenAccount argument. Alice will verify, sign and send the transaction.

2. Post Swap

Return a transaction that you can use from the quote you get from GET /quote.

Try out the endpoint: POST https://api.jup.ag/swap/v6/transaction

**In the example below, we assume the associated token account exists on destinationTokenAccount.

import { PublicKey, Connection, Keypair, VersionedTransaction, VersionedMessage, TransactionMessage } from '@solana/web3.js';
import { getAssociatedTokenAddress, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token';
import fetch from 'node-fetch';

// Replace with actual valid base58 public keys
const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');  // USDC mint address
const bobWalletPublicKey = new PublicKey('BUX7s2ef2htTGb2KKoPHWkmzxPj4nTWMWRgs5CSbQxf9');  // Bob's wallet address

// Establish a connection to the Solana cluster
const connection = new Connection('https://api.mainnet-beta.solana.com');

// Replace these with actual valid base58 public keys
const feeAccount = new PublicKey('ReplaceWithActualValidBase58Key');  // Replace with actual fee account public key
const trackingAccount = new PublicKey('ReplaceWithActualValidBase58Key');  // Replace with actual tracking account public key

// Ensure these are valid base58 strings
console.log("USDC_MINT:", USDC_MINT.toBase58());
console.log("bobWalletPublicKey:", bobWalletPublicKey.toBase58());
console.log("feeAccount:", feeAccount.toBase58());
console.log("trackingAccount:", trackingAccount.toBase58());

// Get the associated token account for Bob's wallet
async function getBobUSDCTokenAccount(bobWalletKeypair) {
  const bobUSDCTokenAccount = await getAssociatedTokenAddress(
    USDC_MINT,
    bobWalletKeypair.publicKey,
    true,
    TOKEN_PROGRAM_ID,
    ASSOCIATED_TOKEN_PROGRAM_ID
  );
  return bobUSDCTokenAccount;
}

// Step 1: Fetch swap info
async function fetchSwapInfo() {
  const response = await fetch('https://api.jup.ag/swap/v6/quote?inputMint=mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&amount=5000000&swapMode=ExactOut&slippageBps=50');
  const data = await response.json();
  return {
    inAmount: data.inAmount,
    otherAmountThreshold: data.otherAmountThreshold,
    quoteResponse: data
  };
}

// Step 2: Fetch the swap transaction
async function fetchSwapTransaction(swapUserKeypair, bobUSDCTokenAccount, swapInfo) {
  const requestBody = {
    userPublicKey: swapUserKeypair.publicKey.toBase58(),
    wrapAndUnwrapSol: true,
    useSharedAccounts: true,
    feeAccount: feeAccount.toBase58(),  // Use actual key
    trackingAccount: trackingAccount.toBase58(),  // Use actual key
    prioritizationFeeLamports: 0,  // No prioritization fee in this case
    asLegacyTransaction: false,
    useTokenLedger: false,
    destinationTokenAccount: bobUSDCTokenAccount.toBase58(),
    dynamicComputeUnitLimit: true,
    skipUserAccountsRpcCalls: true,
    quoteResponse: swapInfo.quoteResponse
  };

  const response = await fetch('https://api.jup.ag/swap/v6/transaction', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(requestBody),
  });

  const { swapTransaction, lastValidBlockHeight } = await response.json();
  return { swapTransaction, lastValidBlockHeight };
}

// Step 3: Send the transaction to the Solana blockchain
async function sendTransaction(swapTransaction, swapUserKeypair, lastValidBlockHeight) {
  const versionedMessage = VersionedMessage.deserialize(Buffer.from(swapTransaction, 'base64'));
  const transaction = new VersionedTransaction(versionedMessage);

  // Get the recent blockhash
  // Using 'finalized' commitment to ensure the blockhash is final and secure
  // You may experiment with 'processed' or 'confirmed' for fetching blockhash to increase speed
  // Reference: https://solana.com/docs/rpc/http/getlatestblockhash
  const bhInfo = await connection.getLatestBlockhashAndContext({ commitment: "finalized" });
  transaction.recentBlockhash = bhInfo.value.blockhash;
  transaction.feePayer = swapUserKeypair.publicKey;

  // Print keys involved in the transaction to diagnose issues
  console.log("Keys in Transaction:", transaction.instructions.flatMap(instr => instr.keys.map(k => k.pubkey.toBase58())));

  // Sign the transaction with the swap user's keypair
  transaction.sign([swapUserKeypair]);

  // Simulate the transaction to ensure it will succeed
  // Using 'finalized' commitment for the simulation to match the security level of the actual send
  // You may experiment with 'confirmed' or 'processed' to simulate faster, but keep in mind the risks
  // Reference: https://solana.com/docs/core/transactions#commitment
  const simulation = await connection.simulateTransaction(transaction, { commitment: "finalized" });
  if (simulation.value.err) {
    throw new Error(`Simulation failed: ${simulation.value.err.toString()}`);
  }

  // Send the transaction
  try {
    const signature = await connection.sendTransaction(transaction, {
      // NOTE: Adjusting maxRetries to a lower value for trading, as 20 retries can be too much
      // Experiment with different maxRetries values based on your tolerance for slippage and speed
      // Reference: https://solana.com/docs/core/transactions#retrying-transactions
      maxRetries: 5,
      skipPreflight: true,
      preflightCommitment: "finalized",
    });

    // Confirm the transaction
    // Using 'finalized' commitment to ensure the transaction is fully confirmed
    // Reference: https://solana.com/docs/core/transactions#confirmation
    const confirmation = await connection.confirmTransaction({
      signature,
      blockhash: bhInfo.value.blockhash,
      lastValidBlockHeight: bhInfo.value.lastValidBlockHeight,
    }, "finalized");

    if (confirmation.value.err) {
      throw new Error(`Transaction not confirmed: ${confirmation.value.err.toString()}`);
    }

    console.log("Confirmed: ", signature);
  } catch (error) {
    console.error("Failed: ", error);
    throw error;
  }
}

// Example usage
(async () => {
  try {
    // Generate keypairs for swap user and Bob's wallet, replace with actual keypairs for real usage
    const swapUserKeypair = Keypair.generate();
    const bobWalletKeypair = Keypair.generate();  // Replace this with Bob's actual keypair

    // Ensure the bobUSDCTokenAccount is correct
    const bobUSDCTokenAccount = await getBobUSDCTokenAccount(bobWalletKeypair);

    // Step 1: Fetch swap info
    const swapInfo = await fetchSwapInfo();

    // Step 2: Fetch the swap transactions
    const { swapTransaction, lastValidBlockHeight } = await fetchSwapTransaction(swapUserKeypair, bobUSDCTokenAccount, swapInfo);

    // Step 3: Send the transaction to the blockchain
    await sendTransaction(swapTransaction, swapUserKeypair, lastValidBlockHeight);
  } catch (error) {
    console.error('Error:', error);
  }
})();

👍

TIP

If you want to add your own fees, check out Add Fee.