@nosana/kit 0.1.7 → 1.0.3
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.
- package/.gitlab-ci.yml +41 -9
- package/.prettierignore +17 -0
- package/README.md +729 -59
- package/dist/config/defaultConfigs.d.ts +1 -1
- package/dist/config/defaultConfigs.js +2 -2
- package/dist/config/types.d.ts +1 -1
- package/dist/config/utils.d.ts +1 -1
- package/dist/config/utils.js +5 -5
- package/dist/errors/NosanaError.d.ts +1 -1
- package/dist/generated_clients/jobs/instructions/assign.d.ts +79 -0
- package/dist/generated_clients/jobs/instructions/assign.js +120 -0
- package/dist/generated_clients/jobs/instructions/extend.d.ts +9 -6
- package/dist/generated_clients/jobs/instructions/extend.js +4 -1
- package/dist/generated_clients/jobs/instructions/finish.d.ts +10 -10
- package/dist/generated_clients/jobs/instructions/finish.js +6 -6
- package/dist/generated_clients/jobs/instructions/index.d.ts +1 -0
- package/dist/generated_clients/jobs/instructions/index.js +1 -0
- package/dist/generated_clients/jobs/programs/nosanaJobs.d.ts +18 -15
- package/dist/generated_clients/jobs/programs/nosanaJobs.js +18 -14
- package/dist/generated_clients/staking/accounts/index.d.ts +9 -0
- package/dist/generated_clients/staking/accounts/index.js +9 -0
- package/dist/generated_clients/staking/accounts/settingsAccount.d.ts +29 -0
- package/dist/generated_clients/staking/accounts/settingsAccount.js +55 -0
- package/dist/generated_clients/staking/accounts/stakeAccount.d.ts +39 -0
- package/dist/generated_clients/staking/accounts/stakeAccount.js +65 -0
- package/dist/generated_clients/staking/errors/index.d.ts +8 -0
- package/dist/generated_clients/staking/errors/index.js +8 -0
- package/dist/generated_clients/staking/errors/nosanaStaking.d.ts +45 -0
- package/dist/generated_clients/staking/errors/nosanaStaking.js +62 -0
- package/dist/generated_clients/staking/index.d.ts +11 -0
- package/dist/generated_clients/staking/index.js +11 -0
- package/dist/generated_clients/staking/instructions/close.d.ts +48 -0
- package/dist/generated_clients/staking/instructions/close.js +81 -0
- package/dist/generated_clients/staking/instructions/extend.d.ts +43 -0
- package/dist/generated_clients/staking/instructions/extend.js +73 -0
- package/dist/generated_clients/staking/instructions/index.d.ts +17 -0
- package/dist/generated_clients/staking/instructions/index.js +17 -0
- package/dist/generated_clients/staking/instructions/init.d.ts +45 -0
- package/dist/generated_clients/staking/instructions/init.js +82 -0
- package/dist/generated_clients/staking/instructions/restake.d.ts +42 -0
- package/dist/generated_clients/staking/instructions/restake.js +70 -0
- package/dist/generated_clients/staking/instructions/slash.d.ts +55 -0
- package/dist/generated_clients/staking/instructions/slash.js +90 -0
- package/dist/generated_clients/staking/instructions/stake.d.ts +64 -0
- package/dist/generated_clients/staking/instructions/stake.js +106 -0
- package/dist/generated_clients/staking/instructions/topup.d.ts +52 -0
- package/dist/generated_clients/staking/instructions/topup.js +87 -0
- package/dist/generated_clients/staking/instructions/unstake.d.ts +42 -0
- package/dist/generated_clients/staking/instructions/unstake.js +70 -0
- package/dist/generated_clients/staking/instructions/updateSettings.d.ts +45 -0
- package/dist/generated_clients/staking/instructions/updateSettings.js +73 -0
- package/dist/generated_clients/staking/instructions/withdraw.d.ts +48 -0
- package/dist/generated_clients/staking/instructions/withdraw.js +81 -0
- package/dist/generated_clients/staking/programs/index.d.ts +8 -0
- package/dist/generated_clients/staking/programs/index.js +8 -0
- package/dist/generated_clients/staking/programs/nosanaStaking.d.ts +53 -0
- package/dist/generated_clients/staking/programs/nosanaStaking.js +71 -0
- package/dist/generated_clients/staking/shared/index.d.ts +49 -0
- package/dist/generated_clients/staking/shared/index.js +86 -0
- package/dist/index.d.ts +12 -4
- package/dist/index.js +18 -136
- package/dist/ipfs/IPFS.js +3 -0
- package/dist/programs/BaseProgram.d.ts +1 -1
- package/dist/programs/BaseProgram.js +3 -7
- package/dist/programs/JobsProgram.d.ts +37 -37
- package/dist/programs/JobsProgram.js +63 -67
- package/dist/programs/StakeProgram.d.ts +29 -0
- package/dist/programs/StakeProgram.js +91 -0
- package/dist/services/NosService.d.ts +48 -0
- package/dist/services/NosService.js +139 -0
- package/dist/{solana/SolanaUtils.d.ts → services/SolanaService.d.ts} +5 -5
- package/dist/{solana/SolanaUtils.js → services/SolanaService.js} +17 -7
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/walletConverter.d.ts +9 -0
- package/dist/utils/walletConverter.js +141 -0
- package/eslint.config.js +47 -0
- package/examples/node/README.md +115 -0
- package/examples/node/nos-service.ts +117 -0
- package/examples/node/package.json +3 -1
- package/examples/node/retrieve.ts +2 -2
- package/examples/node/stake-program.ts +84 -0
- package/package.json +11 -8
- package/scripts/generate-clients.ts +20 -7
- package/vitest.config.ts +31 -0
package/README.md
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
# Nosana Kit
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
21
|
+
// Initialize with mainnet defaults
|
|
27
22
|
const client = new NosanaClient();
|
|
28
23
|
|
|
29
|
-
// Or specify network and
|
|
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('
|
|
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
|
-
//
|
|
42
|
-
const
|
|
36
|
+
// Query jobs with filters
|
|
37
|
+
const completedJobs = await client.jobs.all({
|
|
43
38
|
market: 'market-address',
|
|
44
|
-
state: 2
|
|
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
|
-
###
|
|
59
|
+
### Networks
|
|
51
60
|
|
|
52
61
|
The SDK supports two networks:
|
|
53
62
|
|
|
54
|
-
-
|
|
55
|
-
-
|
|
63
|
+
- **`NosanaNetwork.MAINNET`** - Production network (mainnet-beta)
|
|
64
|
+
- **`NosanaNetwork.DEVNET`** - Development network (devnet)
|
|
56
65
|
|
|
57
|
-
###
|
|
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://
|
|
74
|
+
rpcEndpoint: 'https://api.mainnet-beta.solana.com',
|
|
66
75
|
commitment: 'confirmed'
|
|
67
76
|
},
|
|
68
77
|
ipfs: {
|
|
69
|
-
api: 'https://
|
|
70
|
-
jwt: 'your-jwt-token',
|
|
71
|
-
gateway: 'https://
|
|
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
|
-
##
|
|
87
|
+
## Core Components
|
|
78
88
|
|
|
79
89
|
### NosanaClient
|
|
80
90
|
|
|
81
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
134
|
+
## Jobs Program API
|
|
95
135
|
|
|
96
|
-
|
|
136
|
+
### Fetching Accounts
|
|
97
137
|
|
|
98
|
-
####
|
|
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
|
-
|
|
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
|
-
|
|
171
|
+
```typescript
|
|
108
172
|
async market(address: Address): Promise<Market>
|
|
173
|
+
```
|
|
109
174
|
|
|
110
|
-
|
|
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
|
-
|
|
201
|
+
#### Query All Jobs
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
114
204
|
async all(filters?: {
|
|
115
|
-
state?:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
712
|
+
console.error('RPC connection failed:', error.message);
|
|
150
713
|
break;
|
|
151
|
-
case ErrorCodes.
|
|
152
|
-
console.
|
|
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.
|
|
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)
|