NestDevelopers

Project setup

Configure the dependencies, deployment manifest, IDLs, RPC connection, and wallet signer used by the examples.

All examples use the client defined on this page. They are intended for browser applications with a Solana wallet adapter. The application supplies the RPC connection and wallet signer. Nest publishes the program addresses, IDLs, and oracle update payloads.

Install dependencies

npm install @coral-xyz/anchor@0.31.1 @solana/web3.js@^1.98 @solana/spl-token@^0.4 buffer

The examples use bigint for raw token amounts. nUSD, snUSD, and USDC use six decimals. Read each collateral mint's decimals from its market configuration.

Example file structure

The examples use the following local files. These modules are created by copying the complete examples from the linked documentation pages; they are not imports from an npm package.

src/nest/
├── nest-client.ts              # this page
├── nest-oracle.ts              # Atomic oracle transactions
├── borrowing/
│   ├── read-current-debt.ts
│   ├── deposit-collateral.ts
│   ├── mint-nusd.ts
│   ├── repay-debt.ts
│   └── withdraw-collateral.ts
├── psm/
│   ├── usdc-to-nusd.ts
│   └── nusd-to-usdc.ts
└── staking/
    ├── stake-quote.ts
    ├── nusd-to-snusd.ts
    └── snusd-to-nusd.ts

For example, nest-oracle.ts imports NEST_API_ORIGIN, CollateralMarket, NestClient, and oraclePda from the nest-client.ts created below:

import {
  NEST_API_ORIGIN,
  type CollateralMarket,
  type NestClient,
  oraclePda,
} from "./nest-client";

Shared client

Create src/nest/nest-client.ts:

nest-client.ts
import { AnchorProvider, type Idl, Program, type Wallet } from "@coral-xyz/anchor";
import {
  Connection,
  PublicKey,
  Transaction,
  type Commitment,
} from "@solana/web3.js";
import {
  ASSOCIATED_TOKEN_PROGRAM_ID,
  createAssociatedTokenAccountInstruction,
  getAssociatedTokenAddressSync,
} from "@solana/spl-token";

// Static deployment manifests and Anchor IDLs.
const NEST_DOCS_ORIGIN = "https://docs.nestusd.com";

// Public Nest API for prices, protocol reads, and atomic oracle payloads.
export const NEST_API_ORIGIN = "https://api.nestusd.com";

export type Deployment = {
  schema: "nest-public-deployment-v1";
  cluster: "mainnet-beta";
  programs: { nestCore: string; nestStake: string; nestVaults: string };
  accounts: { protocol: string; stakingState: string };
  mints: { nusd: string; snusd: string; usdc: string };
  tokenPrograms: { nusd: string; snusd: string; usdc: string };
  vaults: {
    psmUsdcVault: string;
    stakingNusdVault: string;
    stakerRevenueNusdVault: string;
    insuranceNusdVault: string;
    protocolRevenueNusdVault: string;
  };
  collateral: CollateralMarket[];
};

export type CollateralMarket = {
  symbol: string;
  displaySymbol: string;
  issuer: string;
  mint: string;
  collateralConfig: string;
  collateralVault: string;
  tokenProgram: string;
  decimals: number;
  borrowLtvBps: number;
  liquidationThresholdBps: number;
  maxConfidenceBps: number;
  maxStalenessSeconds: number;
  oracleProvider: string;
  pythLazerFeedId: number | null;
  signedOracleFeedId: string | null;
};

export type NestClient = {
  connection: Connection;
  wallet: Wallet;
  provider: AnchorProvider;
  deployment: Deployment;
  core: Program;
  stake: Program;
};

async function fetchJson<T>(url: string): Promise<T> {
  const response = await fetch(url, { cache: "no-store" });
  if (!response.ok) throw new Error(`${url} returned HTTP ${response.status}`);
  return response.json() as Promise<T>;
}

export async function createNestClient(
  connection: Connection,
  wallet: Wallet,
): Promise<NestClient> {
  const [deployment, coreIdl, stakeIdl] = await Promise.all([
    fetchJson<Deployment>(`${NEST_DOCS_ORIGIN}/deployments/mainnet.json`),
    fetchJson<Idl>(`${NEST_DOCS_ORIGIN}/idl/nest_core.json`),
    fetchJson<Idl>(`${NEST_DOCS_ORIGIN}/idl/nest_stake.json`),
  ]);

  if (deployment.cluster !== "mainnet-beta") {
    throw new Error(`Unexpected deployment cluster: ${deployment.cluster}`);
  }
  if (coreIdl.address !== deployment.programs.nestCore) {
    throw new Error("Nest Core IDL address does not match the deployment");
  }
  if (stakeIdl.address !== deployment.programs.nestStake) {
    throw new Error("Nest Stake IDL address does not match the deployment");
  }

  const provider = new AnchorProvider(connection, wallet, {
    commitment: "confirmed",
    preflightCommitment: "confirmed",
    skipPreflight: false,
  });

  return {
    connection,
    wallet,
    provider,
    deployment,
    core: new Program(coreIdl, provider),
    stake: new Program(stakeIdl, provider),
  };
}

export function marketBySymbol(client: NestClient, symbol: string) {
  const market = client.deployment.collateral.find((item) => item.symbol === symbol);
  if (!market) throw new Error(`Unsupported Nest collateral symbol: ${symbol}`);
  return market;
}

export function borrowVaultPda(
  coreProgram: PublicKey,
  owner: PublicKey,
  collateralConfig: PublicKey,
) {
  return PublicKey.findProgramAddressSync(
    [Buffer.from("vault"), owner.toBuffer(), collateralConfig.toBuffer()],
    coreProgram,
  )[0];
}

export function oraclePda(coreProgram: PublicKey, collateralConfig: PublicKey) {
  return PublicKey.findProgramAddressSync(
    [Buffer.from("oracle"), collateralConfig.toBuffer()],
    coreProgram,
  )[0];
}

export function pendingWithdrawalPda(
  stakeProgram: PublicKey,
  owner: PublicKey,
  stakingState: PublicKey,
) {
  return PublicKey.findProgramAddressSync(
    [Buffer.from("pending"), owner.toBuffer(), stakingState.toBuffer()],
    stakeProgram,
  )[0];
}

export function ata(mint: PublicKey, owner: PublicKey, tokenProgram: PublicKey) {
  return getAssociatedTokenAddressSync(
    mint,
    owner,
    false,
    tokenProgram,
    ASSOCIATED_TOKEN_PROGRAM_ID,
  );
}

export async function addAtaIfMissing(
  client: NestClient,
  transaction: Transaction,
  mint: PublicKey,
  owner: PublicKey,
  tokenProgram: PublicKey,
) {
  const address = ata(mint, owner, tokenProgram);
  if (!(await client.connection.getAccountInfo(address, "confirmed"))) {
    transaction.add(createAssociatedTokenAccountInstruction(
      owner,
      address,
      owner,
      mint,
      tokenProgram,
      ASSOCIATED_TOKEN_PROGRAM_ID,
    ));
  }
  return address;
}

export async function confirmedChainTime(connection: Connection) {
  const slot = await connection.getSlot("confirmed");
  const timestamp = await connection.getBlockTime(slot);
  if (timestamp === null) throw new Error(`No block time available for slot ${slot}`);
  return BigInt(timestamp);
}

export function toBaseUnits(value: string, decimals: number): bigint {
  if (!/^\d+(\.\d+)?$/.test(value)) throw new Error(`Invalid token amount: ${value}`);
  const [whole, fraction = ""] = value.split(".");
  if (fraction.length > decimals) throw new Error(`Amount has more than ${decimals} decimals`);
  return BigInt(whole) * 10n ** BigInt(decimals)
    + BigInt(fraction.padEnd(decimals, "0") || "0");
}

function assertInstructionsUnchanged(before: Transaction, after: Transaction) {
  if (before.instructions.length !== after.instructions.length) {
    throw new Error("Wallet changed the transaction instruction count");
  }
  before.instructions.forEach((expected, index) => {
    const actual = after.instructions[index];
    const sameKeys = expected.keys.length === actual.keys.length
      && expected.keys.every((key, keyIndex) => {
        const candidate = actual.keys[keyIndex];
        return key.pubkey.equals(candidate.pubkey)
          && key.isSigner === candidate.isSigner
          && key.isWritable === candidate.isWritable;
      });
    if (
      !expected.programId.equals(actual.programId)
      || !expected.data.equals(actual.data)
      || !sameKeys
    ) throw new Error(`Wallet changed instruction ${index}`);
  });
}

export async function simulateSignSend(
  client: NestClient,
  transaction: Transaction,
  commitment: Commitment = "confirmed",
) {
  const latest = await client.connection.getLatestBlockhash(commitment);
  transaction.feePayer = client.wallet.publicKey;
  transaction.recentBlockhash = latest.blockhash;

  const simulation = await client.connection.simulateTransaction(transaction);
  if (simulation.value.err) {
    throw new Error(`Simulation failed: ${JSON.stringify({
      error: simulation.value.err,
      logs: simulation.value.logs,
    })}`);
  }

  const signed = await client.wallet.signTransaction(transaction);
  assertInstructionsUnchanged(transaction, signed);
  const signature = await client.connection.sendRawTransaction(signed.serialize(), {
    skipPreflight: false,
    maxRetries: 10,
    preflightCommitment: commitment,
  });
  const confirmation = await client.connection.confirmTransaction(
    { signature, ...latest },
    commitment,
  );
  if (confirmation.value.err) {
    throw new Error(`Transaction ${signature} failed: ${JSON.stringify(confirmation.value.err)}`);
  }
  return signature;
}

Create the client

Use the RPC connection already configured by your application. Do not embed a paid RPC URL in a public bundle unless the endpoint is intentionally public and rate-limited.

import { Connection } from "@solana/web3.js";
import type { Wallet } from "@coral-xyz/anchor";
import { createNestClient } from "./nest-client";

export async function connectNest(rpcUrl: string, wallet: Wallet) {
  const connection = new Connection(rpcUrl, "confirmed");
  return createNestClient(connection, wallet);
}

Production artifact verification

Fetching the hosted manifest is convenient during development. In production, pin the reviewed manifest and IDLs in your release and alert on upstream changes instead of silently accepting new program addresses.

On this page