NestDevelopers
Borrow nUSD

Mint nUSD

Calculate borrowing capacity and mint nUSD using an atomic collateral-oracle update.

Minting adds principal debt to one collateral position and mints the same raw amount of nUSD to the owner. The program first accrues stability fees, then checks borrow LTV and all debt caps using the freshly refreshed oracle.

Read the current oracle price

Use the public Nest API origin, https://api.nestusd.com, to retrieve the current price used for the pre-transaction quote:

GET https://api.nestusd.com/api/prices

This endpoint returns all configured collateral prices in one prices array. Use ?symbols=SPYx,QQQx when only a subset is needed. Each price includes priceE8, confidenceE8, safePriceE8, publishTime, provider, feed identity, and freshness fields. fetchCurrentOraclePrices() in the oracle helper validates the complete batch against the deployment manifest. Borrowing calculations use safePriceE8 = priceE8 - confidenceE8.

The same price entry includes a transaction-ready oracleUpdate. Use that update immediately to atomically verify and record the quoted price before executing the mint; a second API request is not required.

Required instruction order

Use Atomic oracle transactions to create nest-oracle.ts, then append the mint instruction immediately after the refresh.

TypeScript example

mint-nusd.ts
import { BN } from "@coral-xyz/anchor";
import { PublicKey, Transaction } from "@solana/web3.js";
import { getAccount } from "@solana/spl-token";
import {
  addAtaIfMissing,
  borrowVaultPda,
  confirmedChainTime,
  marketBySymbol,
  oraclePda,
  simulateSignSend,
  toBaseUnits,
  type NestClient,
} from "../nest-client";
import {
  buildOracleRefreshInstructions,
  fetchCurrentOraclePrices,
} from "../nest-oracle";
import { projectStabilityFee } from "./read-current-debt";

const BPS = 10_000n;

function available(limit: bigint, used: bigint) {
  return limit > used ? limit - used : 0n;
}

function minimum(values: bigint[]) {
  return values.reduce((result, value) => value < result ? value : result);
}

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

  const coreProgramId = new PublicKey(client.deployment.programs.nestCore);
  const protocol = new PublicKey(client.deployment.accounts.protocol);
  const collateralConfig = new PublicKey(market.collateralConfig);
  const collateralMint = new PublicKey(market.mint);
  const collateralVault = new PublicKey(market.collateralVault);
  const collateralTokenProgram = new PublicKey(market.tokenProgram);
  const nusdMint = new PublicKey(client.deployment.mints.nusd);
  const nusdTokenProgram = new PublicKey(client.deployment.tokenPrograms.nusd);
  const vault = borrowVaultPda(coreProgramId, owner, collateralConfig);
  const oracle = oraclePda(coreProgramId, collateralConfig);

  const [config, vaultBefore, protocolAccount, currentPrices, nowTs] = await Promise.all([
    client.core.account.collateralConfig.fetch(collateralConfig),
    client.core.account.vault.fetch(vault),
    client.core.account.protocol.fetch(protocol),
    fetchCurrentOraclePrices(client.deployment.collateral),
    confirmedChainTime(client.connection),
  ]);
  if (config.borrowsPaused) throw new Error(`${symbol} borrowing is paused`);
  if (protocolAccount.paused) throw new Error("Nest Core is paused");
  if (BigInt(vaultBefore.collateralRaw.toString()) === 0n) {
    throw new Error("Deposit collateral before minting nUSD");
  }
  const currentPrice = currentPrices.get(market.symbol);
  if (!currentPrice) throw new Error(`No current price for ${market.symbol}`);

  // Calculate the current conservative value and project fees that the mint
  // instruction will checkpoint before checking the debt limits.
  const collateralRaw = BigInt(vaultBefore.collateralRaw.toString());
  const safePriceE8 = BigInt(currentPrice.safePriceE8);
  const collateralValueUsd6 = collateralRaw * safePriceE8 * 1_000_000n
    / (10n ** BigInt(market.decimals) * 100_000_000n);
  const projected = projectStabilityFee({
    principalDebtNusd6: BigInt(vaultBefore.principalDebt.toString()),
    accruedFeeNusd6: BigInt(vaultBefore.accruedFee.toString()),
    lastAccrualTs: BigInt(vaultBefore.lastAccrualTs.toString()),
    stabilityFeeAprBps: BigInt(protocolAccount.stabilityFeeAprBps.toString()),
    nowTs,
  });

  const pendingLiquidationDebt = BigInt(protocolAccount.pendingLiquidationPrincipal.toString())
    + BigInt(protocolAccount.pendingLiquidationFees.toString());
  const protocolReservedDebt = BigInt(protocolAccount.totalDebt.toString())
    + pendingLiquidationDebt
    + projected.pendingFeeNusd6;
  // Nest conservatively reserves all pending liquidation debt against every
  // collateral market until those liquidation receipts settle.
  const marketReservedDebt = BigInt(config.totalDebt.toString())
    + pendingLiquidationDebt
    + projected.pendingFeeNusd6;
  // collateralConfig.protocolDebtCap is the per-collateral-market cap. The
  // field name is retained for on-chain account compatibility.
  const collateralMarketDebtCap = BigInt(config.protocolDebtCap.toString());

  const maximumMintNusd6 = minimum([
    available(
      collateralValueUsd6 * BigInt(config.borrowLtvBps) / BPS,
      projected.projectedDebtNusd6,
    ),
    available(
      BigInt(config.perVaultDebtCap.toString()),
      projected.projectedDebtNusd6,
    ),
    available(BigInt(protocolAccount.protocolDebtCap.toString()), protocolReservedDebt),
    available(collateralMarketDebtCap, marketReservedDebt),
  ]);
  if (amount > maximumMintNusd6) {
    throw new Error(`Requested ${amount}; current maximum is ${maximumMintNusd6} raw nUSD`);
  }

  const transaction = new Transaction();
  const ownerNusdAccount = await addAtaIfMissing(
    client,
    transaction,
    nusdMint,
    owner,
    nusdTokenProgram,
  );
  const prefixCount = transaction.instructions.length;
  transaction.add(...buildOracleRefreshInstructions(
    client,
    market,
    prefixCount,
    currentPrice.oracleUpdate,
  ));
  transaction.add(await client.core.methods
    .mintNusdWithOracle(new BN(amount.toString()))
    .accountsStrict({
      protocol,
      collateralConfig,
      vault,
      oracle,
      collateralMint,
      collateralVault,
      nusdMint,
      ownerNusdAccount,
      owner,
      collateralTokenProgram,
      nusdTokenProgram,
    })
    .instruction());

  const principalBefore = BigInt(vaultBefore.principalDebt.toString());
  const nusdBefore = await client.connection.getAccountInfo(ownerNusdAccount, "confirmed")
    ? (await getAccount(client.connection, ownerNusdAccount, "confirmed", nusdTokenProgram)).amount
    : 0n;
  const signature = await simulateSignSend(client, transaction);

  const [vaultAfter, walletAfter] = await Promise.all([
    client.core.account.vault.fetch(vault),
    getAccount(client.connection, ownerNusdAccount, "confirmed", nusdTokenProgram),
  ]);
  const principalAfter = BigInt(vaultAfter.principalDebt.toString());
  if (principalAfter !== principalBefore + amount || walletAfter.amount !== nusdBefore + amount) {
    throw new Error(`Mint verification failed for ${signature}`);
  }

  return {
    signature,
    vault,
    quotedPriceE8: BigInt(currentPrice.priceE8),
    quotedSafePriceE8: safePriceE8,
    quotedMaximumMintNusd6: maximumMintNusd6,
    mintedNusd6: amount,
    principalDebtNusd6: principalAfter,
  };
}

Maximum mint amount

Use Position health with projected debt from Accrued stability fees:

LTV room = collateral value × borrowLtvBps / 10,000 - projected debt
vault room = perVaultDebtCap - projected debt
protocol room = protocolDebtCap - reserved protocol debt
market room = collateral debt cap - reserved market debt
maximum mint = max(min(LTV room, vault room, protocol room, market room), 0)

Pending liquidation principal and fees are included in reserved debt by the contract. Leave a small buffer below the displayed maximum for price movement, confidence changes, rounding, and fee accrual between quote and execution.

Verification failures

Reject the transaction before signature if the API returns the wrong symbol, feed, provider, signer, or a stale source. Reject it after wallet signing if any instruction or account meta changed. After confirmation, verify both principal debt and the nUSD token-account balance increased by the requested raw amount.

On this page