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
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.