NestDevelopers
Convert USDC and nUSD

Convert USDC into nUSD

Read PSM capacity and convert USDC into nUSD with psm_swap_in.

psm_swap_in(amount) transfers USDC into the canonical PSM vault and mints the same raw amount of nUSD to the user.

TypeScript example

usdc-to-nusd.ts
import { BN } from "@coral-xyz/anchor";
import { PublicKey, Transaction } from "@solana/web3.js";
import { getAccount } from "@solana/spl-token";
import {
  addAtaIfMissing,
  ata,
  simulateSignSend,
  toBaseUnits,
  type NestClient,
} from "../nest-client";

export async function convertUsdcToNusd(client: NestClient, userAmount: string) {
  const owner = client.wallet.publicKey;
  const amount = toBaseUnits(userAmount, 6);
  if (amount <= 0n) throw new Error("Conversion amount must be positive");

  const protocol = new PublicKey(client.deployment.accounts.protocol);
  const usdcMint = new PublicKey(client.deployment.mints.usdc);
  const nusdMint = new PublicKey(client.deployment.mints.nusd);
  const usdcTokenProgram = new PublicKey(client.deployment.tokenPrograms.usdc);
  const nusdTokenProgram = new PublicKey(client.deployment.tokenPrograms.nusd);
  const userUsdcAccount = ata(usdcMint, owner, usdcTokenProgram);
  const psmUsdcVault = new PublicKey(client.deployment.vaults.psmUsdcVault);

  const [protocolBefore, sourceBefore] = await Promise.all([
    client.core.account.protocol.fetch(protocol),
    getAccount(client.connection, userUsdcAccount, "confirmed", usdcTokenProgram),
  ]);
  if (protocolBefore.paused) throw new Error("Nest Core is paused");
  if (BigInt(protocolBefore.psmSwapInFeeBps.toString()) !== 0n) {
    throw new Error("This integration expects the current zero-fee PSM configuration");
  }
  if (sourceBefore.amount < amount) throw new Error("Insufficient USDC balance");
  const liabilitiesBefore = BigInt(protocolBefore.psmUsdcLiabilities.toString());
  const cap = BigInt(protocolBefore.psmCap.toString());
  if (liabilitiesBefore + amount > cap) throw new Error("PSM cap would be exceeded");

  const transaction = new Transaction();
  const userNusdAccount = await addAtaIfMissing(
    client,
    transaction,
    nusdMint,
    owner,
    nusdTokenProgram,
  );
  const destinationBefore = await client.connection.getAccountInfo(userNusdAccount, "confirmed")
    ? (await getAccount(
        client.connection,
        userNusdAccount,
        "confirmed",
        nusdTokenProgram,
      )).amount
    : 0n;

  transaction.add(await client.core.methods
    .psmSwapIn(new BN(amount.toString()))
    .accountsStrict({
      protocol,
      usdcMint,
      nusdMint,
      userUsdcAccount,
      psmUsdcVault,
      userNusdAccount,
      user: owner,
      usdcTokenProgram,
      nusdTokenProgram,
    })
    .instruction());

  const signature = await simulateSignSend(client, transaction);
  const [sourceAfter, destinationAfter, protocolAfter] = await Promise.all([
    getAccount(client.connection, userUsdcAccount, "confirmed", usdcTokenProgram),
    getAccount(client.connection, userNusdAccount, "confirmed", nusdTokenProgram),
    client.core.account.protocol.fetch(protocol),
  ]);
  if (
    sourceAfter.amount !== sourceBefore.amount - amount
    || destinationAfter.amount !== destinationBefore + amount
    || BigInt(protocolAfter.psmUsdcLiabilities.toString()) !== liabilitiesBefore + amount
  ) throw new Error(`PSM conversion verification failed for ${signature}`);

  return { signature, paidUsdc6: amount, receivedNusd6: amount };
}

Quote capacity on-chain

const protocol = await client.core.account.protocol.fetch(
  new PublicKey(client.deployment.accounts.protocol),
);
const remainingRaw = BigInt(protocol.psmCap.toString())
  - BigInt(protocol.psmUsdcLiabilities.toString());

The maximum input is the smaller of remainingRaw and the user's USDC balance. Refetch immediately before building; simulation handles concurrent conversions.

On this page