npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@nosana/kit

v2.0.30

Published

Nosana KIT

Downloads

2,289

Readme

Nosana Kit

TypeScript SDK for interacting with the Nosana Network on Solana. Provides comprehensive tools for managing jobs, markets, runs, and protocol operations on the Nosana decentralized compute network.

v2.0.0 - Major release featuring functional architecture, universal wallet support, and enhanced transaction handling. See CHANGELOG.md for migration guide.

Installation

npm install @nosana/kit

Requirements

  • Node.js >= 20.18.0
  • TypeScript >= 5.3.0 (for development)

Quick Start

import { createNosanaClient, NosanaNetwork } from '@nosana/kit';

// Initialize with mainnet defaults
const client = createNosanaClient();

// Or specify network and configuration
const client = createNosanaClient(NosanaNetwork.DEVNET, {
  solana: {
    rpcEndpoint: 'https://your-custom-rpc.com',
    commitment: 'confirmed',
  },
});

// Fetch a job by address
const job = await client.jobs.get('job-address');
console.log('Job state:', job.state);

// Query jobs with filters
const completedJobs = await client.jobs.all({
  market: 'market-address',
  state: 2, // JobState.COMPLETED
});

Architecture

The SDK uses a functional architecture with factory functions for improved modularity and testability:

  • services/ - Utility services and program interfaces
    • SolanaService - Low-level Solana RPC operations, transactions, and PDA derivations
    • TokenService - Token account operations (configured for NOS token)
    • programs/ - On-chain program interfaces
      • JobsProgram - Jobs, runs, and markets management
      • StakeProgram - Staking account operations
      • MerkleDistributorProgram - Merkle distributor and claim operations
  • ipfs/ - IPFS integration for pinning and retrieving data
  • config/ - Network configurations and defaults
  • utils/ - Helper utilities and type conversions
  • generated_clients/ - Auto-generated Solana program clients (exported as namespaces)

All components use factory functions with explicit dependency injection, making the codebase modular, testable, and maintainable.

Configuration

Networks

The SDK supports two networks:

  • NosanaNetwork.MAINNET - Production network (mainnet-beta)
  • NosanaNetwork.DEVNET - Development network (devnet)

Configuration Options

import { createNosanaClient, NosanaNetwork, LogLevel } from '@nosana/kit';

const client = createNosanaClient(NosanaNetwork.MAINNET, {
  solana: {
    cluster: 'mainnet-beta',
    rpcEndpoint: 'https://api.mainnet-beta.solana.com',
    commitment: 'confirmed',
  },
  ipfs: {
    api: 'https://api.pinata.cloud',
    jwt: 'your-pinata-jwt-token',
    gateway: 'https://gateway.pinata.cloud/ipfs/',
  },
  api: {
    apiKey: 'your-api-key', // Optional: API key for authentication
  },
  logLevel: LogLevel.DEBUG,
  wallet: myWallet, // Optional: Set wallet during initialization (must be a Wallet type)
});

Core Components

NosanaClient

Main entry point for SDK interactions. Created using the createNosanaClient() factory function.

Properties:

  • config: ClientConfig - Active configuration
  • jobs: JobsProgram - Jobs program interface
  • stake: StakeProgram - Staking program interface
  • merkleDistributor: MerkleDistributorProgram - Merkle distributor program interface
  • solana: SolanaService - General Solana utilities (RPC, transactions, PDAs)
  • nos: TokenService - Token operations service (configured for NOS token)
  • api: NosanaApi | undefined - Nosana API client for interacting with Nosana APIs (jobs, credits, markets)
  • ipfs: ReturnType<typeof createIpfsClient> - IPFS operations for pinning and retrieving data
  • authorization: NosanaAuthorization | Omit<NosanaAuthorization, 'generate' | 'generateHeaders'> - Authorization service for message signing and validation
  • logger: Logger - Logging instance
  • wallet?: Wallet - Active wallet (if set). Set this property directly to configure the wallet.

Factory Function:

  • createNosanaClient(network?: NosanaNetwork, customConfig?: PartialClientConfig): NosanaClient - Creates a new client instance

Wallet Configuration

The SDK supports universal wallet configuration through a unified Wallet type that must support both message and transaction signing (MessageSigner & TransactionSigner). This enables compatibility with both browser wallets (wallet-standard) and keypair-based wallets.

Wallet Requirements

The wallet must implement both MessageSigner and TransactionSigner interfaces from @solana/kit. This allows the SDK to use the wallet for:

  • Message signing - For API authentication and authorization
  • Transaction signing - For on-chain operations

Browser Wallets (Wallet-Standard)

Full support for wallet-standard compatible browser wallets (Phantom, Solflare, etc.):

import { createNosanaClient } from '@nosana/kit';
import { useWalletAccountSigner } from '@nosana/solana-vue';

// Create client
const client = createNosanaClient();

// Set browser wallet (wallet-standard compatible)
client.wallet = useWalletAccountSigner(account, currentChain);

Keypair Wallets

Seamless support for keypair-based wallets:

import { createNosanaClient } from '@nosana/kit';
import { generateKeyPairSigner } from '@solana/kit';

// Create client
const client = createNosanaClient();

// Set keypair wallet
const keypair = generateKeyPairSigner();
client.wallet = keypair;

Configuration Options

Wallets can be set at client initialization or dynamically assigned:

import { createNosanaClient, NosanaNetwork } from '@nosana/kit';
import type { Wallet } from '@nosana/kit';

// Option 1: Set wallet during initialization
const client = createNosanaClient(NosanaNetwork.MAINNET, {
  wallet: myWallet,
});

// Option 2: Set wallet dynamically
const client = createNosanaClient();
client.wallet = myWallet;

// Option 3: Change wallet at runtime
client.wallet = anotherWallet;

Type Safety

The SDK leverages @solana/kit types for compile-time safety, ensuring wallet compatibility before runtime.

Jobs Program API

Fetching Accounts

Get Single Job

async get(address: Address, checkRun?: boolean): Promise<Job>

Fetch a job account. If checkRun is true (default), automatically checks for associated run accounts to determine if a queued job is actually running.

const job = await client.jobs.get('job-address');
console.log(job.state); // JobState enum
console.log(job.price); // Job price in smallest unit
console.log(job.ipfsJob); // IPFS CID of job definition
console.log(job.timeStart); // Start timestamp (if running)

Get Single Run

async run(address: Address): Promise<Run>

Fetch a run account by address.

const run = await client.jobs.run('run-address');
console.log(run.job); // Associated job address
console.log(run.node); // Node executing the run
console.log(run.time); // Run start time

Get Single Market

async market(address: Address): Promise<Market>

Fetch a market account by address.

const market = await client.jobs.market('market-address');
console.log(market.queueType); // MarketQueueType enum
console.log(market.jobPrice); // Market job price

Get Multiple Jobs

async multiple(addresses: Address[], checkRuns?: boolean): Promise<Job[]>

Batch fetch multiple jobs by addresses.

const jobs = await client.jobs.multiple(['job-address-1', 'job-address-2', 'job-address-3'], true);

Querying with Filters

Query All Jobs

async all(filters?: {
  state?: JobState,
  market?: Address,
  node?: Address,
  project?: Address
}, checkRuns?: boolean): Promise<Job[]>

Fetch all jobs matching filter criteria using getProgramAccounts.

import { JobState } from '@nosana/kit';

// Get all running jobs in a market
const runningJobs = await client.jobs.all({
  state: JobState.RUNNING,
  market: 'market-address',
});

// Get all jobs for a project
const projectJobs = await client.jobs.all({
  project: 'project-address',
});

Query All Runs

async runs(filters?: {
  job?: Address,
  node?: Address
}): Promise<Run[]>

Fetch runs with optional filtering.

// Get all runs for a specific job
const jobRuns = await client.jobs.runs({ job: 'job-address' });

// Get all runs on a specific node
const nodeRuns = await client.jobs.runs({ node: 'node-address' });

Query All Markets

async markets(): Promise<Market[]>

Fetch all market accounts.

const markets = await client.jobs.markets();

Creating Jobs

Post a Job

async post(params: {
  market: Address,
  timeout: number | bigint,
  ipfsHash: string,
  node?: Address
}): Promise<Instruction>

Create a list instruction for posting a job to a market. Returns an instruction that must be submitted to the network.

// Set wallet first
client.wallet = yourWallet;

// Create job instruction
const instruction = await client.jobs.post({
  market: 'market-address',
  timeout: 3600, // Timeout in seconds
  ipfsHash: 'QmXxx...', // IPFS CID of job definition
  node: 'node-address', // Optional: target specific node
});

// Submit the instruction
await client.solana.buildSignAndSend(instruction);

Real-time Monitoring

Monitor Account Updates

The SDK provides two monitoring methods using async iterators for real-time account updates via WebSocket:

Simple Monitoring (monitor()) - Automatically merges run account data into job events:

async monitor(): Promise<[AsyncIterable<SimpleMonitorEvent>, () => void]>
import { MonitorEventType } from '@nosana/kit';

// Start monitoring
const [eventStream, stop] = await client.jobs.monitor();

// Process events using async iteration
for await (const event of eventStream) {
  if (event.type === MonitorEventType.JOB) {
    console.log('Job update:', event.data.address, event.data.state);
    // event.data will have state, node, and timeStart from run account if it exists
    
    // Process updates - save to database, trigger workflows, etc.
    if (event.data.state === JobState.COMPLETED) {
      await processCompletedJob(event.data);
    }
  } else if (event.type === MonitorEventType.MARKET) {
    console.log('Market update:', event.data.address);
  }
}

// Stop monitoring when done
stop();

Detailed Monitoring (monitorDetailed()) - Provides separate events for job, market, and run accounts:

async monitorDetailed(): Promise<[AsyncIterable<MonitorEvent>, () => void]>
import { MonitorEventType } from '@nosana/kit';

// Start detailed monitoring
const [eventStream, stop] = await client.jobs.monitorDetailed();

// Process events using async iteration
for await (const event of eventStream) {
  switch (event.type) {
    case MonitorEventType.JOB:
      console.log('Job update:', event.data.address);
      break;
    case MonitorEventType.MARKET:
      console.log('Market update:', event.data.address);
      break;
    case MonitorEventType.RUN:
      console.log('Run started:', event.data.job, 'on node', event.data.node);
      break;
  }
}

// Stop monitoring when done
stop();

Both methods handle WebSocket reconnection automatically and continue processing updates until explicitly stopped. The simple monitor() method is recommended for most use cases as it automatically merges run account data into job updates, eliminating the need to manually track run accounts.

Account Types

Job

type Job = {
  address: Address;
  state: JobState; // QUEUED | RUNNING | COMPLETED | STOPPED
  ipfsJob: string | null; // IPFS CID of job definition
  ipfsResult: string | null; // IPFS CID of job result
  market: Address;
  node: Address;
  payer: Address;
  project: Address;
  price: number;
  timeStart: number; // Unix timestamp
  timeEnd: number; // Unix timestamp
  timeout: number; // Seconds
};

enum JobState {
  QUEUED = 0,
  RUNNING = 1,
  COMPLETED = 2,
  STOPPED = 3,
}

Run

type Run = {
  address: Address;
  job: Address; // Associated job
  node: Address; // Node executing the job
  time: number; // Unix timestamp
};

Market

type Market = {
  address: Address;
  queueType: MarketQueueType; // JOB_QUEUE | NODE_QUEUE
  jobPrice: number;
  nodeStakeMinimum: number;
  jobTimeout: number;
  jobType: number;
  project: Address;
  // ... additional fields
};

enum MarketQueueType {
  JOB_QUEUE = 0,
  NODE_QUEUE = 1,
}

Solana Service

General Solana utility service for low-level RPC operations, transactions, and PDA derivations.

Methods

// Build, sign, and send transaction in one call (convenience method)
buildSignAndSend(
  instructions: Instruction | Instruction[],
  options?: {
    feePayer?: TransactionSigner;
    commitment?: 'processed' | 'confirmed' | 'finalized';
  }
): Promise<Signature>

// Build transaction from instructions
buildTransaction(
  instructions: Instruction | Instruction[],
  options?: { feePayer?: TransactionSigner }
): Promise<TransactionMessage & TransactionMessageWithFeePayer & TransactionMessageWithBlockhashLifetime>

// Sign a transaction message
signTransaction(
  transactionMessage: TransactionMessage & TransactionMessageWithFeePayer & TransactionMessageWithBlockhashLifetime
): Promise<SendableTransaction & Transaction & TransactionWithBlockhashLifetime>

// Send and confirm a signed transaction
sendTransaction(
  transaction: SendableTransaction & Transaction & TransactionWithBlockhashLifetime,
  options?: { commitment?: 'processed' | 'confirmed' | 'finalized' }
): Promise<Signature>

// Get account balance
getBalance(address?: Address | string): Promise<bigint>

// Derive program derived address
pda(seeds: Array<Address | string>, programId: Address): Promise<Address>

// Get instruction to transfer SOL
transfer(params: {
  to: Address | string;
  amount: number | bigint;
  from?: TransactionSigner;
}): Promise<Instruction> // Returns TransferSolInstruction

Examples

// Send a single instruction (convenience method)
const signature = await client.solana.buildSignAndSend(instruction);

// Send multiple instructions atomically
const signature = await client.solana.buildSignAndSend([ix1, ix2, ix3]);

// Or build, sign, and send separately for more control
const transactionMessage = await client.solana.buildTransaction(instruction);
const signedTransaction = await client.solana.signTransaction(transactionMessage);
const signature = await client.solana.sendTransaction(signedTransaction);

// Check account balance
const balance = await client.solana.getBalance('address');
console.log(`Balance: ${balance} lamports`);

// Derive PDA
const pda = await client.solana.pda(['seed1', 'seed2'], programAddress);

// Get instruction to transfer SOL
const transferSolIx = await client.solana.transfer({
  to: 'recipient-address',
  amount: 1000000, // lamports (can be number or bigint)
  // from is optional - uses wallet if not provided
});

// Execute the transfer
await client.solana.buildSignAndSend(transferSolIx);

IPFS Service

The IPFS service provides methods to pin data to IPFS and retrieve data from IPFS. It's configured via the ipfs property in the client configuration.

Configuration

const client = createNosanaClient(NosanaNetwork.MAINNET, {
  ipfs: {
    api: 'https://api.pinata.cloud',
    jwt: 'your-pinata-jwt-token',
    gateway: 'https://gateway.pinata.cloud/ipfs/',
  },
});

Methods

// Pin JSON data to IPFS
pin(data: object): Promise<string>

// Pin a file to IPFS
pinFile(filePath: string): Promise<string>

// Retrieve data from IPFS
retrieve(hash: string | Uint8Array): Promise<any>

Examples

// Pin job definition to IPFS
const cid = await client.ipfs.pin({
  version: 1,
  type: 'docker',
  image: 'ubuntu:latest',
  command: ['echo', 'hello'],
});
console.log('Pinned to IPFS:', cid);

// Pin a file to IPFS
const fileCid = await client.ipfs.pinFile('/path/to/file.txt');

// Retrieve job results from IPFS
const results = await client.ipfs.retrieve(job.ipfsResult);
console.log('Job results:', results);

Utility Functions

The SDK also exports utility functions for converting between Solana hash formats and IPFS CIDs:

import { solBytesArrayToIpfsHash, ipfsHashToSolBytesArray } from '@nosana/kit';

// Convert Solana hash bytes to IPFS CID
const ipfsCid = solBytesArrayToIpfsHash(solanaHashBytes);

// Convert IPFS CID to Solana hash bytes
const solanaHash = ipfsHashToSolBytesArray(ipfsCid);

API Service

The API service provides access to Nosana APIs for jobs, credits, and markets. It's automatically configured based on your authentication method.

Authentication Methods

The API service supports two authentication methods:

  1. API Key Authentication (Recommended for server-side applications)

    • Provide an API key in the configuration
    • API key takes precedence over wallet-based authentication
  2. Wallet-Based Authentication (For client-side applications)

    • Set a wallet on the client
    • Uses message signing for authentication
    • Automatically enabled when a wallet is configured

Configuration

// Option 1: Use API key (recommended for servers)
const client = createNosanaClient(NosanaNetwork.MAINNET, {
  api: {
    apiKey: 'your-api-key-here',
  },
});

// Option 2: Use wallet-based auth (for client-side)
const client = createNosanaClient(NosanaNetwork.MAINNET);
client.wallet = myWallet;

// Option 3: API key takes precedence when both are provided
const client = createNosanaClient(NosanaNetwork.MAINNET, {
  api: {
    apiKey: 'your-api-key-here',
  },
  wallet: myWallet, // API key will be used, not wallet
});

Behavior

  • With API Key: API is created immediately with API key authentication
  • With Wallet: API is created when wallet is set, using wallet-based authentication
  • Without Both: API is undefined until either an API key or wallet is provided
  • Priority: If both API key and wallet are provided, API key is used

API Structure

The API service provides access to three main APIs:

client.api?.jobs    // Jobs API
client.api?.credits // Credits API
client.api?.markets // Markets API

Examples

// Using API key
const client = createNosanaClient(NosanaNetwork.MAINNET, {
  api: { apiKey: 'your-api-key' },
});

// API is immediately available
if (client.api) {
  // Use the API
  const jobs = await client.api.jobs.list();
}

// Using wallet-based auth
const client = createNosanaClient(NosanaNetwork.MAINNET);
client.wallet = myWallet;

// API is now available
if (client.api) {
  const credits = await client.api.credits.get();
}

// API updates reactively when wallet changes
client.wallet = undefined; // API becomes undefined
client.wallet = anotherWallet; // API is recreated with new wallet

Authorization Service

The authorization service provides cryptographic message signing and validation using Ed25519 signatures. It's automatically available on the client and adapts based on whether a wallet is configured.

Behavior

  • With Wallet: When a wallet is set, the authorization service provides all methods including generate, validate, generateHeaders, and validateHeaders.
  • Without Wallet: When no wallet is set, the authorization service only provides validate and validateHeaders methods (read-only validation).

Methods

// Generate a signed message (requires wallet)
generate(message: string | Uint8Array, options?: GenerateOptions): Promise<string>

// Validate a signed message
validate(
  message: string | Uint8Array,
  signature: string | Uint8Array,
  publicKey?: string | Uint8Array
): Promise<boolean>

// Generate signed HTTP headers (requires wallet)
generateHeaders(
  method: string,
  path: string,
  body?: string | Uint8Array,
  options?: GenerateHeaderOptions
): Promise<Headers>

// Validate HTTP headers
validateHeaders(headers: Headers | Record<string, string>): Promise<boolean>

Examples

// Set wallet first to enable signing
client.wallet = myWallet;

// Generate a signed message
const signedMessage = await client.authorization.generate('Hello, Nosana!');
console.log('Signed message:', signedMessage);

// Validate a signed message
const isValid = await client.authorization.validate('Hello, Nosana!', signedMessage);
console.log('Message is valid:', isValid);

// Generate signed HTTP headers for API requests
const headers = await client.authorization.generateHeaders(
  'POST',
  '/api/jobs',
  JSON.stringify({ data: 'example' })
);

// Use headers in HTTP request
fetch('https://api.nosana.com/api/jobs', {
  method: 'POST',
  headers: headers,
  body: JSON.stringify({ data: 'example' }),
});

// Validate incoming HTTP headers
const isValidRequest = await client.authorization.validateHeaders(requestHeaders);
if (!isValidRequest) {
  throw new Error('Invalid authorization');
}

Use Cases

  • API Authentication: Sign requests to Nosana APIs using message signatures
  • Message Verification: Verify signed messages from other parties
  • Secure Communication: Establish authenticated communication channels
  • Request Authorization: Validate incoming API requests

Merkle Distributor Program

The MerkleDistributorProgram provides methods to interact with merkle distributor accounts and claim tokens from distributions.

Get a Single Distributor

Fetch a merkle distributor account by its address:

const distributor = await client.merkleDistributor.get('distributor-address');

console.log('Distributor:', distributor.address);
console.log('Admin:', distributor.admin);
console.log('Mint:', distributor.mint);
console.log('Root:', distributor.root);

Get All Distributors

Fetch all merkle distributor accounts:

const distributors = await client.merkleDistributor.all();
console.log(`Found ${distributors.length} distributors`);

Get Claim Status

Fetch claim status for a specific distributor and claimant:

// Get claim status for the wallet's address
const claimStatus =
  await client.merkleDistributor.getClaimStatusForDistributor('distributor-address');

// Or specify a claimant address
const claimStatus = await client.merkleDistributor.getClaimStatusForDistributor(
  'distributor-address',
  'claimant-address'
);

if (claimStatus) {
  console.log('Claimed:', claimStatus.claimed);
  console.log('Amount Unlocked:', claimStatus.amountUnlocked);
  console.log('Amount Locked:', claimStatus.amountLocked);
} else {
  console.log('No claim status found');
}

Get Claim Status PDA

Derive the ClaimStatus PDA address:

// Derive for wallet's address
const pda = await client.merkleDistributor.getClaimStatusPda('distributor-address');

// Or specify a claimant address
const pda = await client.merkleDistributor.getClaimStatusPda(
  'distributor-address',
  'claimant-address'
);

Claim Tokens

Claim tokens from a merkle distributor:

// Set wallet first
client.wallet = yourWallet;

// Claim tokens
const instruction = await client.merkleDistributor.claim({
  distributor: 'distributor-address',
  amountUnlocked: 1000000, // Amount in smallest unit
  amountLocked: 500000,
  proof: [
    /* merkle proof array */
  ],
  target: ClaimTarget.YES, // or ClaimTarget.NO
});

// Submit the instruction
await client.solana.buildSignAndSend(instruction);

Clawback Tokens

Clawback tokens from a merkle distributor (admin only):

// Set wallet first (must be admin)
client.wallet = adminWallet;

// Clawback tokens
const instruction = await client.merkleDistributor.clawback({
  distributor: 'distributor-address',
});

// Submit the instruction
await client.solana.buildSignAndSend(instruction);

Type Definitions

interface MerkleDistributor {
  address: Address;
  admin: Address;
  mint: Address;
  root: string; // Base58 encoded merkle root
  buffer0: string;
  buffer1: string;
  buffer2: string;
  // ... additional fields
}

interface ClaimStatus {
  address: Address;
  distributor: Address;
  claimant: Address;
  claimed: boolean;
  amountUnlocked: number;
  amountLocked: number;
}

enum ClaimTarget {
  YES = 'YES',
  NO = 'NO',
}

Use Cases

  • Airdrop Claims: Allow users to claim tokens from merkle tree distributions
  • Reward Distribution: Distribute rewards to eligible addresses
  • Token Vesting: Manage locked and unlocked token distributions
  • Governance: Distribute governance tokens to eligible participants

Staking Program

The StakeProgram provides methods to interact with Nosana staking accounts on-chain.

Get a Single Stake Account

Fetch a stake account by its address:

const stake = await client.stake.get('stake-account-address');

console.log('Stake Account:', stake.address);
console.log('Authority:', stake.authority);
console.log('Staked Amount:', stake.amount);
console.log('xNOS Tokens:', stake.xnos);
console.log('Duration:', stake.duration);
console.log('Time to Unstake:', stake.timeUnstake);
console.log('Vault:', stake.vault);

Get Multiple Stake Accounts

Fetch multiple stake accounts by their addresses:

const addresses = ['address1', 'address2', 'address3'];
const stakes = await client.stake.multiple(addresses);

stakes.forEach((stake) => {
  console.log(`${stake.address}: ${stake.amount} staked`);
});

Get All Stake Accounts

Fetch all stake accounts in the program:

// Get all stakes
const allStakes = await client.stake.all();
console.log(`Found ${allStakes.length} stake accounts`);

Type Definitions

interface Stake {
  address: Address;
  amount: number;
  authority: Address;
  duration: number;
  timeUnstake: number;
  vault: Address;
  vaultBump: number;
  xnos: number;
}

Use Cases

  • Portfolio Tracking: Monitor your staked NOS tokens
  • Analytics: Analyze staking patterns and distributions
  • Governance: Check voting power based on staked amounts
  • Rewards Calculation: Calculate rewards based on stake duration and amount

Example: Analyze Staking Distribution

const allStakes = await client.stake.all();

// Calculate total staked
const totalStaked = allStakes.reduce((sum, stake) => sum + stake.amount, 0);

// Find average stake
const averageStake = totalStaked / allStakes.length;

// Find largest stake
const largestStake = allStakes.reduce((max, stake) => Math.max(max, stake.amount), 0);

console.log('Staking Statistics:');
console.log(`Total Staked: ${totalStaked.toLocaleString()} NOS`);
console.log(`Average Stake: ${averageStake.toLocaleString()} NOS`);
console.log(`Largest Stake: ${largestStake.toLocaleString()} NOS`);
console.log(`Number of Stakers: ${allStakes.length}`);

Token Service

The TokenService provides methods to interact with token accounts on Solana. In the NosanaClient, it's configured for the NOS token and accessible via client.nos.

Get All Token Holders

Fetch all accounts holding NOS tokens using a single RPC call:

// Get all holders (excludes zero balance accounts by default)
const holders = await client.nos.getAllTokenHolders();

console.log(`Found ${holders.length} NOS token holders`);

holders.forEach((holder) => {
  console.log(`${holder.owner}: ${holder.uiAmount} NOS`);
});

// Include accounts with zero balance
const allAccounts = await client.nos.getAllTokenHolders({ includeZeroBalance: true });
console.log(`Total accounts: ${allAccounts.length}`);

// Exclude PDA accounts (smart contract-owned token accounts)
const userAccounts = await client.nos.getAllTokenHolders({ excludePdaAccounts: true });
console.log(`User-owned accounts: ${userAccounts.length}`);

// Combine filters
const activeUsers = await client.nos.getAllTokenHolders({
  includeZeroBalance: false,
  excludePdaAccounts: true,
});
console.log(`Active user accounts: ${activeUsers.length}`);

Get Token Account for Address

Retrieve the NOS token account for a specific owner:

const account = await client.nos.getTokenAccountForAddress('owner-address');

if (account) {
  console.log('Token Account:', account.pubkey);
  console.log('Owner:', account.owner);
  console.log('Balance:', account.uiAmount, 'NOS');
  console.log('Raw Amount:', account.amount.toString());
  console.log('Decimals:', account.decimals);
} else {
  console.log('No NOS token account found');
}

Get Balance

Convenience method to get just the NOS balance for an address:

const balance = await client.nos.getBalance('owner-address');
console.log(`Balance: ${balance} NOS`);
// Returns 0 if no token account exists

Transfer Tokens

Get instruction(s) to transfer SPL tokens. Returns either 1 or 2 instructions depending on whether the recipient's associated token account needs to be created:

// Get transfer instruction(s)
const instructions = await client.nos.transfer({
  to: 'recipient-address',
  amount: 1000000, // token base units (can be number or bigint)
  // from is optional - uses wallet if not provided
});

// Execute the transfer
// instructions is a tuple:
// - [TransferInstruction] when recipient ATA exists (1 instruction)
// - [CreateAssociatedTokenIdempotentInstruction, TransferInstruction] when ATA needs creation (2 instructions)
await client.solana.buildSignAndSend(instructions);

The function automatically:

  • Finds the sender's associated token account
  • Finds the recipient's associated token account
  • Creates the recipient's ATA if it doesn't exist (returns 2 instructions: create ATA + transfer)
  • Returns only the transfer instruction if the recipient's ATA already exists (returns 1 instruction)

Type Definitions

interface TokenAccount {
  pubkey: Address;
  owner: Address;
  mint: Address;
  amount: bigint;
  decimals: number;
}

interface TokenAccountWithBalance extends TokenAccount {
  uiAmount: number; // Balance with decimals applied
}

Use Cases

  • Analytics: Analyze token distribution and holder statistics
  • Airdrops: Get list of all token holders for campaigns
  • Balance Checks: Check NOS balances for specific addresses
  • Leaderboards: Create holder rankings sorted by balance
  • Monitoring: Track large holder movements

Example: Filter Large Holders

const holders = await client.nos.getAllTokenHolders();

// Find holders with at least 1000 NOS
const largeHolders = holders.filter((h) => h.uiAmount >= 1000);

// Sort by balance descending
largeHolders.sort((a, b) => b.uiAmount - a.uiAmount);

// Display top 10
largeHolders.slice(0, 10).forEach((holder, i) => {
  console.log(`${i + 1}. ${holder.owner}: ${holder.uiAmount.toLocaleString()} NOS`);
});

Error Handling

The SDK provides structured error handling with specific error codes.

NosanaError

class NosanaError extends Error {
  code: string;
  details?: any;
}

Error Codes

enum ErrorCodes {
  INVALID_NETWORK = 'INVALID_NETWORK',
  INVALID_CONFIG = 'INVALID_CONFIG',
  RPC_ERROR = 'RPC_ERROR',
  TRANSACTION_ERROR = 'TRANSACTION_ERROR',
  PROGRAM_ERROR = 'PROGRAM_ERROR',
  VALIDATION_ERROR = 'VALIDATION_ERROR',
  NO_WALLET = 'NO_WALLET',
  FILE_ERROR = 'FILE_ERROR',
  WALLET_CONVERSION_ERROR = 'WALLET_CONVERSION_ERROR',
}

Examples

import { NosanaError, ErrorCodes } from '@nosana/kit';

try {
  const job = await client.jobs.get('invalid-address');
} catch (error) {
  if (error instanceof NosanaError) {
    switch (error.code) {
      case ErrorCodes.RPC_ERROR:
        console.error('RPC connection failed:', error.message);
        break;
      case ErrorCodes.NO_WALLET:
        console.error('Wallet not configured');
        client.wallet = myWallet;
        break;
      case ErrorCodes.TRANSACTION_ERROR:
        console.error('Transaction failed:', error.details);
        break;
      default:
        console.error('Unknown error:', error.message);
    }
  } else {
    throw error; // Re-throw non-Nosana errors
  }
}

Logging

The SDK includes a built-in singleton logger with configurable levels.

Log Levels

enum LogLevel {
  DEBUG = 'debug',
  INFO = 'info',
  WARN = 'warn',
  ERROR = 'error',
  NONE = 'none',
}

Configuration

import { createNosanaClient, LogLevel } from '@nosana/kit';

// Set log level during initialization
const client = createNosanaClient(NosanaNetwork.MAINNET, {
  logLevel: LogLevel.DEBUG,
});

// Access logger directly
client.logger.info('Information message');
client.logger.error('Error message');
client.logger.debug('Debug details');

Testing

The SDK includes comprehensive test coverage.

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Generate coverage report
npm run test:coverage

Development

# Build the SDK
npm run build

# Lint code
npm run lint

# Format code
npm run format

# Generate Solana program clients
npm run generate-clients

TypeScript Support

The SDK is written in TypeScript and provides complete type definitions. All types are exported for use in your applications:

import type {
  Job,
  Run,
  Market,
  JobState,
  MarketQueueType,
  Stake,
  MerkleDistributor,
  ClaimStatus,
  ClaimTarget,
  ClientConfig,
  NosanaClient,
  Wallet,
  Address,
} from '@nosana/kit';

// The `address` utility function is also available for creating typed addresses
import { address } from '@nosana/kit';
const jobAddress = address('your-job-address');

Dependencies

Core dependencies:

  • @solana/kit 5.0.0 - Solana web3 library
  • @solana-program/token 0.8.0 - Token program utilities
  • @solana-program/system 0.10.0 - System program utilities
  • @solana-program/compute-budget 0.11.0 - Compute budget utilities
  • bs58 6.0.0 - Base58 encoding

License

MIT

Links