On-chain queries
Query borrow positions, debt, token balances, staking shares, and pending withdrawals from Solana.
Use API responses to render fast previews, but use Solana accounts for balances, parameters, debt, and transaction decisions. This module queries all supported borrow markets from the deployment manifest without scanning unrelated program accounts.
Portfolio reader
Create read-nest-portfolio.ts beside nest-client.ts:
import { PublicKey } from "@solana/web3.js";
import { getAccount } from "@solana/spl-token";
import {
ata,
borrowVaultPda,
confirmedChainTime,
pendingWithdrawalPda,
type NestClient,
} from "./nest-client";
function bn(value: { toString(): string }) {
return BigInt(value.toString());
}
async function tokenBalanceOrZero(
client: NestClient,
mint: PublicKey,
owner: PublicKey,
tokenProgram: PublicKey,
) {
const address = ata(mint, owner, tokenProgram);
const info = await client.connection.getAccountInfo(address, "confirmed");
if (!info) return { address, amount: 0n };
const account = await getAccount(
client.connection,
address,
"confirmed",
tokenProgram,
);
if (!account.owner.equals(owner) || !account.mint.equals(mint)) {
throw new Error(`Unexpected token account data at ${address}`);
}
return { address, amount: account.amount };
}
export async function readBorrowPositions(client: NestClient, owner: PublicKey) {
const coreProgram = new PublicKey(client.deployment.programs.nestCore);
return Promise.all(client.deployment.collateral.map(async (market) => {
const collateralConfig = new PublicKey(market.collateralConfig);
const address = borrowVaultPda(coreProgram, owner, collateralConfig);
const vault = await client.core.account.vault.fetchNullable(address);
if (!vault) return null;
if (
!vault.owner.equals(owner)
|| !vault.collateralConfig.equals(collateralConfig)
) throw new Error(`Borrow vault identity mismatch at ${address}`);
const principalDebtNusd6 = bn(vault.principalDebt);
const storedFeeNusd6 = bn(vault.accruedFee);
return {
symbol: market.symbol,
address,
collateralConfig,
collateralRaw: bn(vault.collateralRaw),
principalDebtNusd6,
storedFeeNusd6,
storedDebtNusd6: principalDebtNusd6 + storedFeeNusd6,
lastAccrualTs: bn(vault.lastAccrualTs),
};
})).then((positions) => positions.filter((position) => position !== null));
}
export async function readStakingPosition(client: NestClient, owner: PublicKey) {
const stakeProgram = new PublicKey(client.deployment.programs.nestStake);
const stakingStateAddress = new PublicKey(client.deployment.accounts.stakingState);
const pendingAddress = pendingWithdrawalPda(
stakeProgram,
owner,
stakingStateAddress,
);
const snusdMint = new PublicKey(client.deployment.mints.snusd);
const snusdTokenProgram = new PublicKey(client.deployment.tokenPrograms.snusd);
const [state, pending, snusd] = await Promise.all([
client.stake.account.stakingState.fetch(stakingStateAddress),
client.stake.account.pendingWithdrawalAccount.fetchNullable(pendingAddress),
tokenBalanceOrZero(client, snusdMint, owner, snusdTokenProgram),
]);
if (pending && (
!pending.owner.equals(owner)
|| !pending.stakingState.equals(stakingStateAddress)
)) throw new Error(`Pending withdrawal identity mismatch at ${pendingAddress}`);
const totalShares = bn(state.totalShares);
const redeemableNusd6 = bn(state.stakingVaultNusd) > bn(state.reservedPendingClaims)
? bn(state.stakingVaultNusd) - bn(state.reservedPendingClaims)
: 0n;
const walletValueNusd6 = totalShares === 0n
? 0n
: snusd.amount * redeemableNusd6 / totalShares;
return {
stakingStateAddress,
snusdAccount: snusd.address,
walletSharesSnusd6: snusd.amount,
estimatedWalletValueNusd6: walletValueNusd6,
totalShares,
redeemableNusd6,
cooldownSeconds: bn(state.cooldownSeconds),
pending: pending ? {
address: pendingAddress,
sharesSnusd6: bn(pending.shares),
requestTs: bn(pending.requestTs),
claimDeadlineTs: bn(pending.claimDeadlineTs),
} : null,
};
}
export async function readNestPortfolio(client: NestClient, owner: PublicKey) {
const protocolAddress = new PublicKey(client.deployment.accounts.protocol);
const nusdMint = new PublicKey(client.deployment.mints.nusd);
const usdcMint = new PublicKey(client.deployment.mints.usdc);
const nusdTokenProgram = new PublicKey(client.deployment.tokenPrograms.nusd);
const usdcTokenProgram = new PublicKey(client.deployment.tokenPrograms.usdc);
const [chainTime, protocol, borrowPositions, staking, nusd, usdc] =
await Promise.all([
confirmedChainTime(client.connection),
client.core.account.protocol.fetch(protocolAddress),
readBorrowPositions(client, owner),
readStakingPosition(client, owner),
tokenBalanceOrZero(client, nusdMint, owner, nusdTokenProgram),
tokenBalanceOrZero(client, usdcMint, owner, usdcTokenProgram),
]);
return {
owner,
chainTime,
protocol: {
address: protocolAddress,
paused: protocol.paused,
badDebtNusd6: bn(protocol.badDebtNusd),
totalDebtNusd6: bn(protocol.totalDebt),
stabilityFeeAprBps: bn(protocol.stabilityFeeAprBps),
psmSwapInFeeBps: bn(protocol.psmSwapInFeeBps),
psmSwapOutFeeBps: bn(protocol.psmSwapOutFeeBps),
},
balances: {
nusd6: nusd.amount,
snusd6: staking.walletSharesSnusd6,
usdc6: usdc.amount,
},
borrowPositions,
staking,
};
}Project current debt
The stored account does not update every second. Project the fee from
lastAccrualTs to the confirmed chain timestamp using the integer-exact helper
in Accrued stability fees. Do this independently for
each position because each vault has its own last-accrual time.
Read current market parameters
The manifest is discovery data. Before minting or withdrawing, fetch the collateral configuration account and use its current pause flags, caps, LTV, liquidation threshold, oracle limits, totals, and decimals:
export async function readCurrentMarket(client: NestClient, config: string) {
const address = new PublicKey(config);
const account = await client.core.account.collateralConfig.fetch(address);
if (!account.protocol.equals(new PublicKey(client.deployment.accounts.protocol))) {
throw new Error("Collateral config belongs to another protocol account");
}
return { address, account };
}Do not use JavaScript numbers for token accounting
Keep raw amounts, prices, timestamps, basis points, debt, and shares as
bigint or Anchor BN. Convert to decimal strings only at the UI boundary.