@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/kitRequirements
- 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 interfacesSolanaService- Low-level Solana RPC operations, transactions, and PDA derivationsTokenService- Token account operations (configured for NOS token)programs/- On-chain program interfacesJobsProgram- Jobs, runs, and markets managementStakeProgram- Staking account operationsMerkleDistributorProgram- Merkle distributor and claim operations
ipfs/- IPFS integration for pinning and retrieving dataconfig/- Network configurations and defaultsutils/- Helper utilities and type conversionsgenerated_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 configurationjobs: JobsProgram- Jobs program interfacestake: StakeProgram- Staking program interfacemerkleDistributor: MerkleDistributorProgram- Merkle distributor program interfacesolana: 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 dataauthorization: NosanaAuthorization | Omit<NosanaAuthorization, 'generate' | 'generateHeaders'>- Authorization service for message signing and validationlogger: Logger- Logging instancewallet?: 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 timeGet 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 priceGet 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 TransferSolInstructionExamples
// 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:
API Key Authentication (Recommended for server-side applications)
- Provide an API key in the configuration
- API key takes precedence over wallet-based authentication
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
undefineduntil 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 APIExamples
// 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 walletAuthorization 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, andvalidateHeaders. - Without Wallet: When no wallet is set, the authorization service only provides
validateandvalidateHeadersmethods (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 existsTransfer 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:coverageDevelopment
# Build the SDK
npm run build
# Lint code
npm run lint
# Format code
npm run format
# Generate Solana program clients
npm run generate-clientsTypeScript 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/kit5.0.0 - Solana web3 library@solana-program/token0.8.0 - Token program utilities@solana-program/system0.10.0 - System program utilities@solana-program/compute-budget0.11.0 - Compute budget utilitiesbs586.0.0 - Base58 encoding
License
MIT
