@nosana/kit 0.1.6 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.gitlab-ci.yml +41 -9
  2. package/.prettierignore +17 -0
  3. package/README.md +729 -59
  4. package/dist/config/defaultConfigs.d.ts +1 -1
  5. package/dist/config/defaultConfigs.js +2 -2
  6. package/dist/config/types.d.ts +3 -1
  7. package/dist/config/utils.d.ts +1 -1
  8. package/dist/config/utils.js +14 -5
  9. package/dist/errors/NosanaError.d.ts +1 -1
  10. package/dist/generated_clients/jobs/instructions/assign.d.ts +79 -0
  11. package/dist/generated_clients/jobs/instructions/assign.js +120 -0
  12. package/dist/generated_clients/jobs/instructions/extend.d.ts +9 -6
  13. package/dist/generated_clients/jobs/instructions/extend.js +4 -1
  14. package/dist/generated_clients/jobs/instructions/finish.d.ts +10 -10
  15. package/dist/generated_clients/jobs/instructions/finish.js +6 -6
  16. package/dist/generated_clients/jobs/instructions/index.d.ts +1 -0
  17. package/dist/generated_clients/jobs/instructions/index.js +1 -0
  18. package/dist/generated_clients/jobs/programs/nosanaJobs.d.ts +18 -15
  19. package/dist/generated_clients/jobs/programs/nosanaJobs.js +18 -14
  20. package/dist/generated_clients/staking/accounts/index.d.ts +9 -0
  21. package/dist/generated_clients/staking/accounts/index.js +9 -0
  22. package/dist/generated_clients/staking/accounts/settingsAccount.d.ts +29 -0
  23. package/dist/generated_clients/staking/accounts/settingsAccount.js +55 -0
  24. package/dist/generated_clients/staking/accounts/stakeAccount.d.ts +39 -0
  25. package/dist/generated_clients/staking/accounts/stakeAccount.js +65 -0
  26. package/dist/generated_clients/staking/errors/index.d.ts +8 -0
  27. package/dist/generated_clients/staking/errors/index.js +8 -0
  28. package/dist/generated_clients/staking/errors/nosanaStaking.d.ts +45 -0
  29. package/dist/generated_clients/staking/errors/nosanaStaking.js +62 -0
  30. package/dist/generated_clients/staking/index.d.ts +11 -0
  31. package/dist/generated_clients/staking/index.js +11 -0
  32. package/dist/generated_clients/staking/instructions/close.d.ts +48 -0
  33. package/dist/generated_clients/staking/instructions/close.js +81 -0
  34. package/dist/generated_clients/staking/instructions/extend.d.ts +43 -0
  35. package/dist/generated_clients/staking/instructions/extend.js +73 -0
  36. package/dist/generated_clients/staking/instructions/index.d.ts +17 -0
  37. package/dist/generated_clients/staking/instructions/index.js +17 -0
  38. package/dist/generated_clients/staking/instructions/init.d.ts +45 -0
  39. package/dist/generated_clients/staking/instructions/init.js +82 -0
  40. package/dist/generated_clients/staking/instructions/restake.d.ts +42 -0
  41. package/dist/generated_clients/staking/instructions/restake.js +70 -0
  42. package/dist/generated_clients/staking/instructions/slash.d.ts +55 -0
  43. package/dist/generated_clients/staking/instructions/slash.js +90 -0
  44. package/dist/generated_clients/staking/instructions/stake.d.ts +64 -0
  45. package/dist/generated_clients/staking/instructions/stake.js +106 -0
  46. package/dist/generated_clients/staking/instructions/topup.d.ts +52 -0
  47. package/dist/generated_clients/staking/instructions/topup.js +87 -0
  48. package/dist/generated_clients/staking/instructions/unstake.d.ts +42 -0
  49. package/dist/generated_clients/staking/instructions/unstake.js +70 -0
  50. package/dist/generated_clients/staking/instructions/updateSettings.d.ts +45 -0
  51. package/dist/generated_clients/staking/instructions/updateSettings.js +73 -0
  52. package/dist/generated_clients/staking/instructions/withdraw.d.ts +48 -0
  53. package/dist/generated_clients/staking/instructions/withdraw.js +81 -0
  54. package/dist/generated_clients/staking/programs/index.d.ts +8 -0
  55. package/dist/generated_clients/staking/programs/index.js +8 -0
  56. package/dist/generated_clients/staking/programs/nosanaStaking.d.ts +53 -0
  57. package/dist/generated_clients/staking/programs/nosanaStaking.js +71 -0
  58. package/dist/generated_clients/staking/shared/index.d.ts +49 -0
  59. package/dist/generated_clients/staking/shared/index.js +86 -0
  60. package/dist/index.d.ts +14 -4
  61. package/dist/index.js +20 -136
  62. package/dist/ipfs/IPFS.d.ts +33 -2
  63. package/dist/ipfs/IPFS.js +110 -5
  64. package/dist/programs/BaseProgram.d.ts +1 -1
  65. package/dist/programs/BaseProgram.js +3 -7
  66. package/dist/programs/JobsProgram.d.ts +37 -37
  67. package/dist/programs/JobsProgram.js +63 -67
  68. package/dist/programs/StakeProgram.d.ts +29 -0
  69. package/dist/programs/StakeProgram.js +91 -0
  70. package/dist/services/NosService.d.ts +48 -0
  71. package/dist/services/NosService.js +134 -0
  72. package/dist/{solana/SolanaUtils.d.ts → services/SolanaService.d.ts} +5 -5
  73. package/dist/{solana/SolanaUtils.js → services/SolanaService.js} +17 -7
  74. package/dist/utils/index.d.ts +2 -1
  75. package/dist/utils/index.js +2 -0
  76. package/dist/utils/walletConverter.d.ts +9 -0
  77. package/dist/utils/walletConverter.js +141 -0
  78. package/eslint.config.js +49 -0
  79. package/examples/node/README.md +115 -0
  80. package/examples/node/nos-service.ts +117 -0
  81. package/examples/node/package.json +3 -1
  82. package/examples/node/retrieve.ts +2 -2
  83. package/examples/node/stake-program.ts +84 -0
  84. package/package.json +13 -8
  85. package/scripts/generate-clients.ts +20 -7
  86. package/vitest.config.ts +31 -0
package/README.md CHANGED
@@ -1,16 +1,6 @@
1
1
  # Nosana Kit
2
2
 
3
- A TypeScript SDK for interacting with the Nosana Network on Solana. This kit provides a comprehensive set of tools for managing jobs, markets, runs, and other Nosana protocol operations.
4
-
5
- ## Features
6
-
7
- - 🚀 **Full TypeScript Support** - Complete type safety and IntelliSense
8
- - 🔗 **Solana Integration** - Built on top of the new Solana Kit client
9
- - 📦 **Jobs Management** - Create, manage, and monitor nosana jobs
10
- - 🌐 **IPFS** - Seamless IPFS hash handling and conversions, uploading and retrieving
11
- - ⚙️ **Configurable Networks** - Support for mainnet and devnet
12
- - 🧪 **Comprehensive Testing** - Well-tested with high coverage
13
- - 📝 **Rich Logging** - Built-in logging with configurable levels
3
+ 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.
14
4
 
15
5
  ## Installation
16
6
 
@@ -18,15 +8,20 @@ A TypeScript SDK for interacting with the Nosana Network on Solana. This kit pro
18
8
  npm install @nosana/kit
19
9
  ```
20
10
 
11
+ ### Requirements
12
+
13
+ - Node.js >= 20.18.0
14
+ - TypeScript >= 5.3.0 (for development)
15
+
21
16
  ## Quick Start
22
17
 
23
18
  ```typescript
24
19
  import { NosanaClient, NosanaNetwork } from '@nosana/kit';
25
20
 
26
- // Initialize client with default mainnet configuration
21
+ // Initialize with mainnet defaults
27
22
  const client = new NosanaClient();
28
23
 
29
- // Or specify network and custom configuration
24
+ // Or specify network and configuration
30
25
  const client = new NosanaClient(NosanaNetwork.DEVNET, {
31
26
  solana: {
32
27
  rpcEndpoint: 'https://your-custom-rpc.com',
@@ -34,27 +29,41 @@ const client = new NosanaClient(NosanaNetwork.DEVNET, {
34
29
  }
35
30
  });
36
31
 
37
- // Fetch a job
38
- const job = await client.jobs.get('your-job-address');
32
+ // Fetch a job by address
33
+ const job = await client.jobs.get('job-address');
39
34
  console.log('Job state:', job.state);
40
35
 
41
- // Get all completed jobs for a market
42
- const jobs = await client.jobs.all({
36
+ // Query jobs with filters
37
+ const completedJobs = await client.jobs.all({
43
38
  market: 'market-address',
44
- state: 2 // completed jobs
39
+ state: 2 // JobState.COMPLETED
45
40
  });
46
41
  ```
47
42
 
43
+ ## Architecture
44
+
45
+ The SDK is organized into clear, purpose-driven modules:
46
+
47
+ - **`services/`** - Utility services (SolanaService for RPC/transactions, NosService for NOS token operations)
48
+ - **`programs/`** - On-chain program interfaces (JobsProgram, StakeProgram)
49
+ - **`ipfs/`** - IPFS integration
50
+ - **`config/`** - Network configurations
51
+ - **`utils/`** - Helper utilities
52
+
53
+ This structure makes it clear that:
54
+ - **Services** = Helper services and utilities for common operations
55
+ - **Programs** = Interfaces to on-chain Solana programs
56
+
48
57
  ## Configuration
49
58
 
50
- ### Network Configuration
59
+ ### Networks
51
60
 
52
61
  The SDK supports two networks:
53
62
 
54
- - `NosanaNetwork.MAINNET` - Production network
55
- - `NosanaNetwork.DEVNET` - Development network
63
+ - **`NosanaNetwork.MAINNET`** - Production network (mainnet-beta)
64
+ - **`NosanaNetwork.DEVNET`** - Development network (devnet)
56
65
 
57
- ### Custom Configuration
66
+ ### Configuration Options
58
67
 
59
68
  ```typescript
60
69
  import { NosanaClient, NosanaNetwork, NosanaLogLevel } from '@nosana/kit';
@@ -62,80 +71,634 @@ import { NosanaClient, NosanaNetwork, NosanaLogLevel } from '@nosana/kit';
62
71
  const client = new NosanaClient(NosanaNetwork.MAINNET, {
63
72
  solana: {
64
73
  cluster: 'mainnet-beta',
65
- rpcEndpoint: 'https://your-rpc-endpoint.com',
74
+ rpcEndpoint: 'https://api.mainnet-beta.solana.com',
66
75
  commitment: 'confirmed'
67
76
  },
68
77
  ipfs: {
69
- api: 'https://your-ipfs-api.com',
70
- jwt: 'your-jwt-token',
71
- gateway: 'https://your-ipfs-gateway.com'
78
+ api: 'https://api.pinata.cloud',
79
+ jwt: 'your-pinata-jwt-token',
80
+ gateway: 'https://gateway.pinata.cloud/ipfs/'
72
81
  },
73
- logLevel: NosanaLogLevel.DEBUG
82
+ logLevel: NosanaLogLevel.DEBUG,
83
+ wallet: keypairArray // Optional: Set wallet during initialization
74
84
  });
75
85
  ```
76
86
 
77
- ## API Reference
87
+ ## Core Components
78
88
 
79
89
  ### NosanaClient
80
90
 
81
- The main client class that provides access to all Nosana protocol features.
91
+ Main entry point for SDK interactions.
92
+
93
+ **Properties:**
94
+ - `config: ClientConfig` - Active configuration
95
+ - `jobs: JobsProgram` - Jobs program interface
96
+ - `stake: StakeProgram` - Staking program interface
97
+ - `solana: SolanaService` - General Solana utilities (RPC, transactions, PDAs)
98
+ - `nos: NosService` - NOS token operations service
99
+ - `ipfs: IPFS` - IPFS operations and utilities
100
+ - `logger: Logger` - Logging instance
101
+ - `wallet?: KeyPairSigner` - Active wallet (if set)
102
+
103
+ **Methods:**
104
+ - `setWallet(wallet: WalletConfig): Promise<KeyPairSigner>` - Set the signing wallet
105
+
106
+ ### Wallet Configuration
107
+
108
+ The SDK supports multiple wallet input formats:
82
109
 
83
110
  ```typescript
84
- class NosanaClient {
85
- constructor(network?: NosanaNetwork, customConfig?: PartialClientConfig)
86
-
87
- readonly config: ClientConfig
88
- readonly jobs: JobsProgram
89
- readonly solana: SolanaUtils
90
- readonly logger: Logger
91
- }
111
+ // Number array (raw keypair)
112
+ await client.setWallet([1, 2, 3, ..., 64]);
113
+
114
+ // JSON string
115
+ await client.setWallet(JSON.stringify(keypairArray));
116
+
117
+ // Base58 string
118
+ await client.setWallet('base58EncodedPrivateKey');
119
+
120
+ // File path
121
+ await client.setWallet('/path/to/keypair.json');
122
+
123
+ // Environment variable (JSON array or base58)
124
+ process.env.SOLANA_PRIVATE_KEY = JSON.stringify(keypairArray);
125
+ await client.setWallet('env:SOLANA_PRIVATE_KEY');
126
+
127
+ // Browser wallet adapter
128
+ await client.setWallet(browserWalletAdapter);
129
+
130
+ // Existing KeyPairSigner (passthrough)
131
+ await client.setWallet(existingKeypairSigner);
92
132
  ```
93
133
 
94
- ### JobsProgram
134
+ ## Jobs Program API
95
135
 
96
- Manages job-related operations on the Nosana network.
136
+ ### Fetching Accounts
97
137
 
98
- #### Methods
138
+ #### Get Single Job
99
139
 
100
140
  ```typescript
101
- // Fetch a single job
102
141
  async get(address: Address, checkRun?: boolean): Promise<Job>
142
+ ```
103
143
 
104
- // Fetch a run
144
+ Fetch a job account. If `checkRun` is true (default), automatically checks for associated run accounts to determine if a queued job is actually running.
145
+
146
+ ```typescript
147
+ const job = await client.jobs.get('job-address');
148
+ console.log(job.state); // JobState enum
149
+ console.log(job.price); // Job price in smallest unit
150
+ console.log(job.ipfsJob); // IPFS CID of job definition
151
+ console.log(job.timeStart); // Start timestamp (if running)
152
+ ```
153
+
154
+ #### Get Single Run
155
+
156
+ ```typescript
105
157
  async run(address: Address): Promise<Run>
158
+ ```
159
+
160
+ Fetch a run account by address.
161
+
162
+ ```typescript
163
+ const run = await client.jobs.run('run-address');
164
+ console.log(run.job); // Associated job address
165
+ console.log(run.node); // Node executing the run
166
+ console.log(run.time); // Run start time
167
+ ```
168
+
169
+ #### Get Single Market
106
170
 
107
- // Fetch a market
171
+ ```typescript
108
172
  async market(address: Address): Promise<Market>
173
+ ```
109
174
 
110
- // Fetch multiple jobs
175
+ Fetch a market account by address.
176
+
177
+ ```typescript
178
+ const market = await client.jobs.market('market-address');
179
+ console.log(market.queueType); // MarketQueueType enum
180
+ console.log(market.jobPrice); // Market job price
181
+ ```
182
+
183
+ #### Get Multiple Jobs
184
+
185
+ ```typescript
111
186
  async multiple(addresses: Address[], checkRuns?: boolean): Promise<Job[]>
187
+ ```
188
+
189
+ Batch fetch multiple jobs by addresses.
190
+
191
+ ```typescript
192
+ const jobs = await client.jobs.multiple([
193
+ 'job-address-1',
194
+ 'job-address-2',
195
+ 'job-address-3'
196
+ ], true);
197
+ ```
198
+
199
+ ### Querying with Filters
112
200
 
113
- // Fetch all jobs with optional filters
201
+ #### Query All Jobs
202
+
203
+ ```typescript
114
204
  async all(filters?: {
115
- state?: number,
205
+ state?: JobState,
116
206
  market?: Address,
117
207
  node?: Address,
118
- project?: Address,
208
+ project?: Address
119
209
  }, checkRuns?: boolean): Promise<Job[]>
210
+ ```
211
+
212
+ Fetch all jobs matching filter criteria using getProgramAccounts.
213
+
214
+ ```typescript
215
+ import { JobState } from '@nosana/kit';
120
216
 
121
- // Get runs with optional filters
217
+ // Get all running jobs in a market
218
+ const runningJobs = await client.jobs.all({
219
+ state: JobState.RUNNING,
220
+ market: 'market-address'
221
+ });
222
+
223
+ // Get all jobs for a project
224
+ const projectJobs = await client.jobs.all({
225
+ project: 'project-address'
226
+ });
227
+ ```
228
+
229
+ #### Query All Runs
230
+
231
+ ```typescript
122
232
  async runs(filters?: {
123
233
  job?: Address,
124
- node?: Address,
234
+ node?: Address
125
235
  }): Promise<Run[]>
236
+ ```
126
237
 
127
- // Get markets with optional filters
238
+ Fetch runs with optional filtering.
239
+
240
+ ```typescript
241
+ // Get all runs for a specific job
242
+ const jobRuns = await client.jobs.runs({ job: 'job-address' });
243
+
244
+ // Get all runs on a specific node
245
+ const nodeRuns = await client.jobs.runs({ node: 'node-address' });
246
+ ```
247
+
248
+ #### Query All Markets
249
+
250
+ ```typescript
128
251
  async markets(filters?: {
129
- project?: Address,
252
+ project?: Address
130
253
  }): Promise<Market[]>
131
254
  ```
132
255
 
133
- #### Account Types
134
- ...
256
+ Fetch markets with optional project filtering.
257
+
258
+ ```typescript
259
+ const projectMarkets = await client.jobs.markets({
260
+ project: 'project-address'
261
+ });
262
+ ```
263
+
264
+ ### Creating Jobs
265
+
266
+ #### Post a Job
267
+
268
+ ```typescript
269
+ async post(params: {
270
+ market: Address,
271
+ timeout: number | bigint,
272
+ ipfsHash: string,
273
+ node?: Address
274
+ }): Promise<Instruction>
275
+ ```
276
+
277
+ Create a list instruction for posting a job to a market. Returns an instruction that must be submitted to the network.
278
+
279
+ ```typescript
280
+ // Set wallet first
281
+ await client.setWallet(yourKeypair);
282
+
283
+ // Create job instruction
284
+ const instruction = await client.jobs.post({
285
+ market: 'market-address',
286
+ timeout: 3600, // Timeout in seconds
287
+ ipfsHash: 'QmXxx...', // IPFS CID of job definition
288
+ node: 'node-address' // Optional: target specific node
289
+ });
290
+
291
+ // Submit the instruction
292
+ await client.solana.send(instruction);
293
+ ```
294
+
295
+ ### Real-time Monitoring
296
+
297
+ #### Monitor Account Updates
298
+
299
+ ```typescript
300
+ async monitor(options?: {
301
+ onJobAccount?: (job: Job) => void | Promise<void>,
302
+ onMarketAccount?: (market: Market) => void | Promise<void>,
303
+ onRunAccount?: (run: Run) => void | Promise<void>,
304
+ onError?: (error: Error, accountType?: string) => void | Promise<void>
305
+ }): Promise<() => void>
306
+ ```
307
+
308
+ Subscribe to real-time account updates via WebSocket. Includes automatic reconnection on failure.
309
+
310
+ ```typescript
311
+ // Start monitoring
312
+ const stopMonitoring = await client.jobs.monitor({
313
+ onJobAccount: async (job) => {
314
+ console.log('Job update:', job.address, job.state);
315
+
316
+ // Process updates - save to database, trigger workflows, etc.
317
+ if (job.state === JobState.COMPLETED) {
318
+ await processCompletedJob(job);
319
+ }
320
+ },
321
+ onRunAccount: async (run) => {
322
+ console.log('Run started:', run.job, 'on node', run.node);
323
+ },
324
+ onError: (error) => {
325
+ console.error('Monitor error:', error);
326
+ }
327
+ });
328
+
329
+ // Stop monitoring when done
330
+ stopMonitoring();
331
+ ```
332
+
333
+ The monitor handles WebSocket reconnection automatically and continues processing updates until explicitly stopped.
334
+
335
+ ## Account Types
336
+
337
+ ### Job
338
+
339
+ ```typescript
340
+ type Job = {
341
+ address: Address;
342
+ state: JobState; // QUEUED | RUNNING | COMPLETED | STOPPED
343
+ ipfsJob: string | null; // IPFS CID of job definition
344
+ ipfsResult: string | null; // IPFS CID of job result
345
+ market: Address;
346
+ node: Address;
347
+ payer: Address;
348
+ project: Address;
349
+ price: number;
350
+ timeStart: number; // Unix timestamp
351
+ timeEnd: number; // Unix timestamp
352
+ timeout: number; // Seconds
353
+ };
354
+
355
+ enum JobState {
356
+ QUEUED = 0,
357
+ RUNNING = 1,
358
+ COMPLETED = 2,
359
+ STOPPED = 3
360
+ }
361
+ ```
362
+
363
+ ### Run
364
+
365
+ ```typescript
366
+ type Run = {
367
+ address: Address;
368
+ job: Address; // Associated job
369
+ node: Address; // Node executing the job
370
+ time: number; // Unix timestamp
371
+ };
372
+ ```
373
+
374
+ ### Market
375
+
376
+ ```typescript
377
+ type Market = {
378
+ address: Address;
379
+ queueType: MarketQueueType; // JOB_QUEUE | NODE_QUEUE
380
+ jobPrice: number;
381
+ nodeStakeMinimum: number;
382
+ jobTimeout: number;
383
+ jobType: number;
384
+ project: Address;
385
+ // ... additional fields
386
+ };
387
+
388
+ enum MarketQueueType {
389
+ JOB_QUEUE = 0,
390
+ NODE_QUEUE = 1
391
+ }
392
+ ```
393
+
394
+ ## Solana Service
395
+
396
+ General Solana utility service for low-level RPC operations, transactions, and PDA derivations.
397
+
398
+ ### Methods
399
+
400
+ ```typescript
401
+ // Send transaction (instruction, array, or transaction object)
402
+ send(tx: IInstruction | IInstruction[] | Transaction): Promise<Signature>
403
+
404
+ // Get account balance
405
+ getBalance(address: Address | string): Promise<bigint>
406
+
407
+ // Get latest blockhash
408
+ getLatestBlockhash(): Promise<{ blockhash: string, lastValidBlockHeight: number }>
409
+
410
+ // Derive program derived address
411
+ pda(seeds: Array<Address | string>, programId: Address): Promise<Address>
412
+ ```
413
+
414
+ ### Examples
415
+
416
+ ```typescript
417
+ // Send a single instruction
418
+ const signature = await client.solana.send(instruction);
419
+
420
+ // Send multiple instructions atomically
421
+ const signature = await client.solana.send([ix1, ix2, ix3]);
422
+
423
+ // Check account balance
424
+ const balance = await client.solana.getBalance('address');
425
+ console.log(`Balance: ${balance} lamports`);
426
+
427
+ // Derive PDA
428
+ const pda = await client.solana.pda(
429
+ ['seed1', 'seed2'],
430
+ programAddress
431
+ );
432
+ ```
433
+
434
+ ## IPFS Utilities
435
+
436
+ ### Static Methods
437
+
438
+ ```typescript
439
+ // Convert Solana hash to IPFS CID
440
+ IPFS.solHashToIpfsHash(hash: Uint8Array | number[]): string | null
441
+
442
+ // Convert IPFS hash to byte array
443
+ IPFS.IpfsHashToByteArray(ipfsHash: string): Uint8Array
444
+ ```
445
+
446
+ ### Instance Methods
447
+
448
+ ```typescript
449
+ // Pin JSON data to Pinata
450
+ pin(data: object, name?: string): Promise<string>
451
+
452
+ // Pin file to Pinata
453
+ pinFile(filePath: string, name?: string): Promise<string>
454
+
455
+ // Pin buffer/stream to Pinata
456
+ pinFileFromBuffer(source: Buffer | Readable, filename: string): Promise<string>
457
+
458
+ // Retrieve data from IPFS
459
+ retrieve(hash: string | Uint8Array, options?: AxiosRequestConfig): Promise<any>
460
+ ```
461
+
462
+ ### Examples
463
+
464
+ ```typescript
465
+ // Convert between Solana and IPFS hash formats
466
+ const ipfsCid = IPFS.solHashToIpfsHash(solanaHashBytes);
467
+ const solanaHash = IPFS.IpfsHashToByteArray(ipfsCid);
468
+
469
+ // Pin job definition
470
+ const cid = await client.ipfs.pin({
471
+ version: 1,
472
+ type: 'docker',
473
+ image: 'ubuntu:latest',
474
+ command: ['echo', 'hello']
475
+ }, 'job-definition');
476
+
477
+ // Retrieve job results
478
+ const results = await client.ipfs.retrieve(job.ipfsResult);
479
+ ```
480
+
481
+ ## Staking Program
482
+
483
+ The StakeProgram provides methods to interact with Nosana staking accounts on-chain.
484
+
485
+ ### Get a Single Stake Account
486
+
487
+ Fetch a stake account by its address:
488
+
489
+ ```typescript
490
+ const stake = await client.stake.get('stake-account-address');
491
+
492
+ console.log('Stake Account:', stake.address);
493
+ console.log('Authority:', stake.authority);
494
+ console.log('Staked Amount:', stake.amount);
495
+ console.log('xNOS Tokens:', stake.xnos);
496
+ console.log('Duration:', stake.duration);
497
+ console.log('Time to Unstake:', stake.timeUnstake);
498
+ console.log('Vault:', stake.vault);
499
+ ```
500
+
501
+ ### Get Multiple Stake Accounts
502
+
503
+ Fetch multiple stake accounts by their addresses:
504
+
505
+ ```typescript
506
+ const addresses = ['address1', 'address2', 'address3'];
507
+ const stakes = await client.stake.multiple(addresses);
508
+
509
+ stakes.forEach(stake => {
510
+ console.log(`${stake.address}: ${stake.amount} staked`);
511
+ });
512
+ ```
513
+
514
+ ### Get All Stake Accounts
515
+
516
+ Fetch all stake accounts in the program:
517
+
518
+ ```typescript
519
+ // Get all stakes
520
+ const allStakes = await client.stake.all();
521
+ console.log(`Found ${allStakes.length} stake accounts`);
522
+ ```
523
+
524
+ ### Type Definitions
525
+
526
+ ```typescript
527
+ interface Stake {
528
+ address: Address;
529
+ amount: number;
530
+ authority: Address;
531
+ duration: number;
532
+ timeUnstake: number;
533
+ vault: Address;
534
+ vaultBump: number;
535
+ xnos: number;
536
+ }
537
+ ```
538
+
539
+ ### Use Cases
540
+
541
+ - **Portfolio Tracking**: Monitor your staked NOS tokens
542
+ - **Analytics**: Analyze staking patterns and distributions
543
+ - **Governance**: Check voting power based on staked amounts
544
+ - **Rewards Calculation**: Calculate rewards based on stake duration and amount
545
+
546
+ ### Example: Analyze Staking Distribution
547
+
548
+ ```typescript
549
+ const allStakes = await client.stake.all();
550
+
551
+ // Calculate total staked
552
+ const totalStaked = allStakes.reduce((sum, stake) => sum + stake.amount, 0);
553
+
554
+ // Find average stake
555
+ const averageStake = totalStaked / allStakes.length;
556
+
557
+ // Find largest stake
558
+ const largestStake = allStakes.reduce((max, stake) =>
559
+ Math.max(max, stake.amount), 0
560
+ );
561
+
562
+ console.log('Staking Statistics:');
563
+ console.log(`Total Staked: ${totalStaked.toLocaleString()} NOS`);
564
+ console.log(`Average Stake: ${averageStake.toLocaleString()} NOS`);
565
+ console.log(`Largest Stake: ${largestStake.toLocaleString()} NOS`);
566
+ console.log(`Number of Stakers: ${allStakes.length}`);
567
+ ```
568
+
569
+ ## NOS Token Service
570
+
571
+ The NosService provides methods to interact with NOS token accounts on Solana.
572
+
573
+ ### Get All Token Holders
574
+
575
+ Fetch all accounts holding NOS tokens using a single RPC call:
576
+
577
+ ```typescript
578
+ // Get all holders (excludes zero balance accounts by default)
579
+ const holders = await client.nos.getAllTokenHolders();
580
+
581
+ console.log(`Found ${holders.length} NOS token holders`);
582
+
583
+ holders.forEach(holder => {
584
+ console.log(`${holder.owner}: ${holder.uiAmount} NOS`);
585
+ });
586
+
587
+ // Include accounts with zero balance
588
+ const allAccounts = await client.nos.getAllTokenHolders({ includeZeroBalance: true });
589
+ console.log(`Total accounts: ${allAccounts.length}`);
590
+
591
+ // Exclude PDA accounts (smart contract-owned token accounts)
592
+ const userAccounts = await client.nos.getAllTokenHolders({ excludePdaAccounts: true });
593
+ console.log(`User-owned accounts: ${userAccounts.length}`);
594
+
595
+ // Combine filters
596
+ const activeUsers = await client.nos.getAllTokenHolders({
597
+ includeZeroBalance: false,
598
+ excludePdaAccounts: true
599
+ });
600
+ console.log(`Active user accounts: ${activeUsers.length}`);
601
+ ```
602
+
603
+ ### Get Token Account for Address
604
+
605
+ Retrieve the NOS token account for a specific owner:
606
+
607
+ ```typescript
608
+ const account = await client.nos.getTokenAccountForAddress('owner-address');
609
+
610
+ if (account) {
611
+ console.log('Token Account:', account.pubkey);
612
+ console.log('Owner:', account.owner);
613
+ console.log('Balance:', account.uiAmount, 'NOS');
614
+ console.log('Raw Amount:', account.amount.toString());
615
+ console.log('Decimals:', account.decimals);
616
+ } else {
617
+ console.log('No NOS token account found');
618
+ }
619
+ ```
620
+
621
+ ### Get Balance
622
+
623
+ Convenience method to get just the NOS balance for an address:
624
+
625
+ ```typescript
626
+ const balance = await client.nos.getBalance('owner-address');
627
+ console.log(`Balance: ${balance} NOS`);
628
+ // Returns 0 if no token account exists
629
+ ```
630
+
631
+ ### Type Definitions
632
+
633
+ ```typescript
634
+ interface TokenAccount {
635
+ pubkey: Address;
636
+ owner: Address;
637
+ mint: Address;
638
+ amount: bigint;
639
+ decimals: number;
640
+ }
641
+
642
+ interface TokenAccountWithBalance extends TokenAccount {
643
+ uiAmount: number; // Balance with decimals applied
644
+ }
645
+ ```
646
+
647
+ ### Use Cases
648
+
649
+ - **Analytics**: Analyze token distribution and holder statistics
650
+ - **Airdrops**: Get list of all token holders for campaigns
651
+ - **Balance Checks**: Check NOS balances for specific addresses
652
+ - **Leaderboards**: Create holder rankings sorted by balance
653
+ - **Monitoring**: Track large holder movements
654
+
655
+ ### Example: Filter Large Holders
656
+
657
+ ```typescript
658
+ const holders = await client.nos.getAllTokenHolders();
659
+
660
+ // Find holders with at least 1000 NOS
661
+ const largeHolders = holders.filter(h => h.uiAmount >= 1000);
662
+
663
+ // Sort by balance descending
664
+ largeHolders.sort((a, b) => b.uiAmount - a.uiAmount);
665
+
666
+ // Display top 10
667
+ largeHolders.slice(0, 10).forEach((holder, i) => {
668
+ console.log(`${i + 1}. ${holder.owner}: ${holder.uiAmount.toLocaleString()} NOS`);
669
+ });
670
+ ```
135
671
 
136
672
  ## Error Handling
137
673
 
138
- The SDK provides structured error handling with specific error codes:
674
+ The SDK provides structured error handling with specific error codes.
675
+
676
+ ### NosanaError
677
+
678
+ ```typescript
679
+ class NosanaError extends Error {
680
+ code: string;
681
+ details?: any;
682
+ }
683
+ ```
684
+
685
+ ### Error Codes
686
+
687
+ ```typescript
688
+ enum ErrorCodes {
689
+ INVALID_NETWORK = 'INVALID_NETWORK',
690
+ INVALID_CONFIG = 'INVALID_CONFIG',
691
+ RPC_ERROR = 'RPC_ERROR',
692
+ TRANSACTION_ERROR = 'TRANSACTION_ERROR',
693
+ PROGRAM_ERROR = 'PROGRAM_ERROR',
694
+ VALIDATION_ERROR = 'VALIDATION_ERROR',
695
+ NO_WALLET = 'NO_WALLET',
696
+ FILE_ERROR = 'FILE_ERROR',
697
+ WALLET_CONVERSION_ERROR = 'WALLET_CONVERSION_ERROR'
698
+ }
699
+ ```
700
+
701
+ ### Examples
139
702
 
140
703
  ```typescript
141
704
  import { NosanaError, ErrorCodes } from '@nosana/kit';
@@ -146,14 +709,121 @@ try {
146
709
  if (error instanceof NosanaError) {
147
710
  switch (error.code) {
148
711
  case ErrorCodes.RPC_ERROR:
149
- console.log('RPC connection failed');
712
+ console.error('RPC connection failed:', error.message);
150
713
  break;
151
- case ErrorCodes.INVALID_CONFIG:
152
- console.log('Invalid configuration');
714
+ case ErrorCodes.NO_WALLET:
715
+ console.error('Wallet not configured');
716
+ await client.setWallet(keypair);
717
+ break;
718
+ case ErrorCodes.TRANSACTION_ERROR:
719
+ console.error('Transaction failed:', error.details);
153
720
  break;
154
721
  default:
155
- console.log('Unknown error:', error.message);
722
+ console.error('Unknown error:', error.message);
156
723
  }
724
+ } else {
725
+ throw error; // Re-throw non-Nosana errors
157
726
  }
158
727
  }
159
- ```
728
+ ```
729
+
730
+ ## Logging
731
+
732
+ The SDK includes a built-in singleton logger with configurable levels.
733
+
734
+ ### Log Levels
735
+
736
+ ```typescript
737
+ enum NosanaLogLevel {
738
+ DEBUG = 'debug',
739
+ INFO = 'info',
740
+ WARN = 'warn',
741
+ ERROR = 'error',
742
+ NONE = 'none'
743
+ }
744
+ ```
745
+
746
+ ### Configuration
747
+
748
+ ```typescript
749
+ import { NosanaClient, NosanaLogLevel } from '@nosana/kit';
750
+
751
+ // Set log level during initialization
752
+ const client = new NosanaClient(NosanaNetwork.MAINNET, {
753
+ logLevel: NosanaLogLevel.DEBUG
754
+ });
755
+
756
+ // Access logger directly
757
+ client.logger.info('Information message');
758
+ client.logger.error('Error message');
759
+ client.logger.debug('Debug details');
760
+ ```
761
+
762
+ ## Testing
763
+
764
+ The SDK includes comprehensive test coverage.
765
+
766
+ ```bash
767
+ # Run tests
768
+ npm test
769
+
770
+ # Run tests in watch mode
771
+ npm run test:watch
772
+
773
+ # Generate coverage report
774
+ npm run test:coverage
775
+ ```
776
+
777
+ ## Development
778
+
779
+ ```bash
780
+ # Build the SDK
781
+ npm run build
782
+
783
+ # Lint code
784
+ npm run lint
785
+
786
+ # Format code
787
+ npm run format
788
+
789
+ # Generate Solana program clients
790
+ npm run generate-clients
791
+ ```
792
+
793
+ ## TypeScript Support
794
+
795
+ The SDK is written in TypeScript and provides complete type definitions. All types are exported for use in your applications:
796
+
797
+ ```typescript
798
+ import type {
799
+ Job,
800
+ Run,
801
+ Market,
802
+ JobState,
803
+ MarketQueueType,
804
+ ClientConfig,
805
+ NosanaClient,
806
+ KeyPairSigner,
807
+ Address
808
+ } from '@nosana/kit';
809
+ ```
810
+
811
+ ## Dependencies
812
+
813
+ Core dependencies:
814
+ - `gill` ^0.9.0 - Solana web3 library
815
+ - `@solana-program/token` ^0.5.1 - Token program utilities
816
+ - `axios` ^1.6.0 - HTTP client
817
+ - `bs58` ^6.0.0 - Base58 encoding
818
+ - `form-data` ^4.0.0 - Multipart form data
819
+
820
+ ## License
821
+
822
+ MIT
823
+
824
+ ## Links
825
+
826
+ - [Nosana Documentation](https://docs.nosana.com)
827
+ - [Nosana Network](https://nosana.com)
828
+ - [GitHub Repository](https://github.com/nosana-ci/nosana-kit)
829
+ - [NPM Package](https://www.npmjs.com/package/@nosana/kit)