@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
@@ -1,12 +1,14 @@
1
- import { createSolanaClient, address, getProgramDerivedAddress, getAddressEncoder, createTransaction, signTransactionMessageWithSigners, getExplorerLink, getSignatureFromTransaction } from 'gill';
1
+ import { createSolanaClient, address, getProgramDerivedAddress, getAddressEncoder, createTransaction, signTransactionMessageWithSigners, getExplorerLink, getSignatureFromTransaction, } from 'gill';
2
2
  import { NosanaError, ErrorCodes } from '../errors/NosanaError.js';
3
- export class SolanaUtils {
3
+ export class SolanaService {
4
4
  constructor(sdk) {
5
5
  this.sdk = sdk;
6
6
  const rpcEndpoint = this.sdk.config.solana.rpcEndpoint;
7
7
  if (!rpcEndpoint)
8
8
  throw new NosanaError('RPC URL is required', ErrorCodes.INVALID_CONFIG);
9
- const { rpc, rpcSubscriptions, sendAndConfirmTransaction } = createSolanaClient({ urlOrMoniker: rpcEndpoint });
9
+ const { rpc, rpcSubscriptions, sendAndConfirmTransaction } = createSolanaClient({
10
+ urlOrMoniker: rpcEndpoint,
11
+ });
10
12
  this.rpc = rpc;
11
13
  this.rpcSubscriptions = rpcSubscriptions;
12
14
  this.sendAndConfirmTransaction = sendAndConfirmTransaction;
@@ -15,7 +17,7 @@ export class SolanaUtils {
15
17
  const addressEncoder = getAddressEncoder();
16
18
  const [pda] = await getProgramDerivedAddress({
17
19
  programAddress: programId,
18
- seeds: seeds.map(seed => typeof seed !== 'string' ? addressEncoder.encode(seed) : seed),
20
+ seeds: seeds.map((seed) => (typeof seed !== 'string' ? addressEncoder.encode(seed) : seed)),
19
21
  });
20
22
  return pda;
21
23
  }
@@ -45,13 +47,21 @@ export class SolanaUtils {
45
47
  * Type guard to check if the input is a transaction
46
48
  */
47
49
  isTransaction(input) {
48
- return 'instructions' in input && 'version' in input && !('programAddress' in input);
50
+ return (typeof input === 'object' &&
51
+ input !== null &&
52
+ 'instructions' in input &&
53
+ 'version' in input &&
54
+ !('programAddress' in input));
49
55
  }
50
56
  /**
51
57
  * Type guard to check if the input is a signed transaction
52
58
  */
53
59
  isSignedTransaction(input) {
54
- return 'signatures' in input && input.signatures && input.signatures.length > 0;
60
+ return (typeof input === 'object' &&
61
+ input !== null &&
62
+ 'signatures' in input &&
63
+ Array.isArray(input.signatures) &&
64
+ input.signatures.length > 0);
55
65
  }
56
66
  /**
57
67
  * Create, sign, and send a transaction with proper logging and error handling
@@ -95,7 +105,7 @@ export class SolanaUtils {
95
105
  // Log the transaction explorer link
96
106
  const explorerLink = getExplorerLink({
97
107
  cluster: this.sdk.config.solana.cluster,
98
- transaction: signature
108
+ transaction: signature,
99
109
  });
100
110
  this.sdk.logger.info(`Sending transaction: ${explorerLink}`);
101
111
  // Send and confirm the transaction
@@ -16,4 +16,5 @@ export type ConvertTypesForDb<T> = {
16
16
  * @param obj Object that may contain bigint values
17
17
  * @returns Object with all bigint values converted to numbers
18
18
  */
19
- export declare function convertBigIntToNumber<T extends Record<string, any>>(obj: T): ConvertBigIntToNumber<T>;
19
+ export declare function convertBigIntToNumber<T extends Record<string, unknown>>(obj: T): ConvertBigIntToNumber<T>;
20
+ export * from './walletConverter.js';
@@ -12,3 +12,5 @@ export function convertBigIntToNumber(obj) {
12
12
  }
13
13
  return result;
14
14
  }
15
+ // Export wallet conversion utilities
16
+ export * from './walletConverter.js';
@@ -0,0 +1,9 @@
1
+ import { KeyPairSigner } from 'gill';
2
+ import { WalletConfig } from '../config/index.js';
3
+ /**
4
+ * Converts various wallet configurations to a KeyPairSigner
5
+ * @param wallet The wallet configuration to convert
6
+ * @returns A KeyPairSigner instance
7
+ * @throws NosanaError if conversion fails
8
+ */
9
+ export declare function convertWalletConfigToKeyPairSigner(wallet: WalletConfig): Promise<KeyPairSigner>;
@@ -0,0 +1,141 @@
1
+ import { Buffer } from 'buffer';
2
+ import { createKeyPairSignerFromBytes } from 'gill';
3
+ import { NosanaError, ErrorCodes } from '../errors/NosanaError.js';
4
+ import { Logger } from '../logger/Logger.js';
5
+ import bs58 from 'bs58';
6
+ /**
7
+ * Converts various wallet configurations to a KeyPairSigner
8
+ * @param wallet The wallet configuration to convert
9
+ * @returns A KeyPairSigner instance
10
+ * @throws NosanaError if conversion fails
11
+ */
12
+ export async function convertWalletConfigToKeyPairSigner(wallet) {
13
+ const logger = Logger.getInstance();
14
+ try {
15
+ // Check if we already have a KeyPairSigner type
16
+ if (wallet && typeof wallet === 'object' && 'address' in wallet && 'signMessages' in wallet) {
17
+ return wallet;
18
+ }
19
+ // Check if it's a browser wallet adapter (has publicKey and signTransaction/signMessage)
20
+ if (wallet &&
21
+ typeof wallet === 'object' &&
22
+ 'publicKey' in wallet &&
23
+ ('signTransaction' in wallet || 'signMessage' in wallet)) {
24
+ // Convert browser wallet adapter to KeyPairSigner-like interface
25
+ const browserWallet = wallet;
26
+ wallet = {
27
+ address: browserWallet.publicKey.toString(),
28
+ signMessages: async (messages) => {
29
+ if (browserWallet.signMessage) {
30
+ return Promise.all(messages.map((msg) => browserWallet.signMessage(msg)));
31
+ }
32
+ throw new Error('Browser wallet does not support message signing');
33
+ },
34
+ signTransactions: async (transactions) => {
35
+ if (browserWallet.signTransaction) {
36
+ return Promise.all(transactions.map((tx) => browserWallet.signTransaction(tx)));
37
+ }
38
+ throw new Error('Browser wallet does not support transaction signing');
39
+ },
40
+ };
41
+ return wallet;
42
+ }
43
+ // If it's a string, try multiple conversion methods
44
+ if (typeof wallet === 'string') {
45
+ // Only try file/environment loading in Node.js environment
46
+ if (typeof window === 'undefined') {
47
+ try {
48
+ // Use string concatenation to avoid bundler resolving this import at build time
49
+ const nodeModule = 'gill' + '/node';
50
+ const { loadKeypairSignerFromFile, loadKeypairSignerFromEnvironment, loadKeypairSignerFromEnvironmentBase58, } = await import(nodeModule);
51
+ // Try to load from file path
52
+ if (await isValidFilePath(wallet)) {
53
+ try {
54
+ wallet = await loadKeypairSignerFromFile(wallet);
55
+ return wallet;
56
+ }
57
+ catch (error) {
58
+ logger.debug(`Failed to load keypair from file: ${error}`);
59
+ }
60
+ }
61
+ // Try to load from environment variable
62
+ try {
63
+ wallet = await loadKeypairSignerFromEnvironment(wallet);
64
+ return wallet;
65
+ }
66
+ catch (error) {
67
+ logger.debug(`Failed to load keypair from environment: ${error}`);
68
+ }
69
+ // Try to load from environment variable as base58
70
+ try {
71
+ wallet = await loadKeypairSignerFromEnvironmentBase58(wallet);
72
+ return wallet;
73
+ }
74
+ catch (error) {
75
+ logger.debug(`Failed to load keypair from environment base58: ${error}`);
76
+ }
77
+ }
78
+ catch (error) {
79
+ logger.debug(`Node.js modules not available: ${error}`);
80
+ }
81
+ }
82
+ // Try to parse as JSON array
83
+ if (wallet.startsWith('[')) {
84
+ try {
85
+ const key = JSON.parse(wallet);
86
+ wallet = await createKeyPairSignerFromBytes(new Uint8Array(key));
87
+ return wallet;
88
+ }
89
+ catch (error) {
90
+ logger.debug(`Failed to parse as JSON array: ${error}`);
91
+ }
92
+ }
93
+ // Try to decode as base58
94
+ try {
95
+ const key = Buffer.from(bs58.decode(wallet)).toJSON().data;
96
+ wallet = await createKeyPairSignerFromBytes(new Uint8Array(key));
97
+ return wallet;
98
+ }
99
+ catch (error) {
100
+ logger.debug(`Failed to decode as base58: ${error}`);
101
+ }
102
+ }
103
+ // If it's an array, try to create from bytes
104
+ if (Array.isArray(wallet)) {
105
+ try {
106
+ wallet = await createKeyPairSignerFromBytes(new Uint8Array(wallet));
107
+ return wallet;
108
+ }
109
+ catch (error) {
110
+ logger.debug(`Failed to create from byte array: ${error}`);
111
+ }
112
+ }
113
+ // If we get here, none of the conversion methods worked
114
+ throw new Error('Unable to convert wallet to KeyPairSigner using any available method');
115
+ }
116
+ catch (error) {
117
+ throw new NosanaError(`Failed to convert wallet to KeyPairSigner: ${error instanceof Error ? error.message : 'Unknown error'}`, ErrorCodes.WALLET_CONVERSION_ERROR, error);
118
+ }
119
+ }
120
+ /**
121
+ * Checks if a file path is valid and accessible
122
+ * @param filePath The file path to validate
123
+ * @returns True if the file path is valid and accessible
124
+ */
125
+ async function isValidFilePath(filePath) {
126
+ // Only validate file paths in Node.js environment
127
+ if (typeof window !== 'undefined') {
128
+ return false; // Browser environment, no file system access
129
+ }
130
+ try {
131
+ const [fs, path] = await Promise.all([import('fs'), import('path')]);
132
+ if (!path.isAbsolute(filePath) && !filePath.startsWith('./') && !filePath.startsWith('../')) {
133
+ return false;
134
+ }
135
+ const stats = await fs.promises.stat(filePath);
136
+ return stats.isFile();
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ }
@@ -0,0 +1,49 @@
1
+ import tseslint from '@typescript-eslint/eslint-plugin';
2
+ import tsparser from '@typescript-eslint/parser';
3
+
4
+ export default [
5
+ {
6
+ ignores: [
7
+ 'dist/',
8
+ 'node_modules/',
9
+ 'coverage/',
10
+ 'src/generated_clients/',
11
+ 'examples/',
12
+ '**/*.config.js',
13
+ '**/*.config.ts',
14
+ ],
15
+ },
16
+ {
17
+ files: ['**/*.ts'],
18
+ languageOptions: {
19
+ parser: tsparser,
20
+ parserOptions: {
21
+ ecmaVersion: 2020,
22
+ sourceType: 'module',
23
+ },
24
+ },
25
+ plugins: {
26
+ '@typescript-eslint': tseslint,
27
+ },
28
+ rules: {
29
+ ...tseslint.configs.recommended.rules,
30
+ '@typescript-eslint/explicit-function-return-type': 'off',
31
+ '@typescript-eslint/no-explicit-any': 'warn',
32
+ '@typescript-eslint/no-unused-vars': [
33
+ 'error',
34
+ { argsIgnorePattern: '^_' },
35
+ ],
36
+ 'no-console': ['warn', { allow: ['warn', 'error', 'debug', 'info'] }],
37
+ },
38
+ },
39
+ {
40
+ // More lenient rules for test files
41
+ files: ['tests/**/*.ts', '**/*.test.ts', '**/*.spec.ts'],
42
+ rules: {
43
+ '@typescript-eslint/no-explicit-any': 'off',
44
+ '@typescript-eslint/no-unused-vars': 'warn',
45
+ 'no-console': 'off',
46
+ },
47
+ },
48
+ ];
49
+
@@ -128,6 +128,121 @@ Comprehensive unit tests have been created in `src/__tests__/setWallet.test.ts`
128
128
 
129
129
  **Note**: Due to Jest/TypeScript configuration complexities with the `gill/dist/node` imports, running the tests may require additional setup. The functionality works correctly at runtime.
130
130
 
131
+ ## NOS Token Service Example
132
+
133
+ The `nos-service.ts` example demonstrates how to interact with NOS token accounts using the NosService.
134
+
135
+ ### Features
136
+
137
+ 1. **Get all NOS token holders** - Fetch all token accounts holding NOS tokens (excludes zero balances by default)
138
+ 2. **Include zero balance accounts** - Optional flag to include accounts with zero balance
139
+ 3. **Exclude PDA accounts** - Filter out smart contract-owned token accounts (PDAs)
140
+ 4. **Get token account for address** - Retrieve token account details for a specific owner
141
+ 5. **Get balance** - Convenience method to get just the NOS balance
142
+ 6. **Batch queries** - Fetch balances for multiple addresses
143
+ 7. **Filter and analyze** - Find large holders and analyze token distribution
144
+
145
+ ### Running the Example
146
+
147
+ ```bash
148
+ # Navigate to the examples/node directory
149
+ cd examples/node
150
+
151
+ # Install dependencies
152
+ npm install
153
+
154
+ # Run the nos-service example
155
+ npm run nos-service
156
+ ```
157
+
158
+ ### Example Usage
159
+
160
+ ```typescript
161
+ import { NosanaClient, NosanaNetwork } from '@nosana/kit';
162
+
163
+ const client = new NosanaClient(NosanaNetwork.MAINNET);
164
+
165
+ // Get all token holders (single RPC call, excludes zero balances by default)
166
+ const holders = await client.nos.getAllTokenHolders();
167
+ console.log(`Found ${holders.length} NOS token holders`);
168
+
169
+ // Include accounts with zero balance
170
+ const allAccounts = await client.nos.getAllTokenHolders({ includeZeroBalance: true });
171
+ console.log(`Total accounts: ${allAccounts.length}`);
172
+
173
+ // Exclude PDA accounts (smart contract-owned accounts)
174
+ const userAccounts = await client.nos.getAllTokenHolders({ excludePdaAccounts: true });
175
+ console.log(`User-owned accounts: ${userAccounts.length}`);
176
+
177
+ // Get token account for specific address
178
+ const account = await client.nos.getTokenAccountForAddress('owner-address');
179
+ if (account) {
180
+ console.log(`Balance: ${account.uiAmount} NOS`);
181
+ }
182
+
183
+ // Get just the balance (convenience method)
184
+ const balance = await client.nos.getBalance('owner-address');
185
+ console.log(`Balance: ${balance} NOS`);
186
+
187
+ // Filter holders by minimum balance
188
+ const largeHolders = holders.filter(holder => holder.uiAmount >= 1000);
189
+ ```
190
+
191
+ ### Use Cases
192
+
193
+ - **Analytics**: Analyze token distribution and holder statistics
194
+ - **Airdrops**: Get list of all token holders for airdrop campaigns
195
+ - **Balance checks**: Check NOS balances for specific addresses
196
+ - **Leaderboards**: Create holder leaderboards sorted by balance
197
+ - **Monitoring**: Track large holder movements
198
+
199
+ ## Stake Program Example
200
+
201
+ The `stake-program.ts` example demonstrates how to interact with Nosana staking accounts using the StakeProgram.
202
+
203
+ ### Features
204
+
205
+ 1. **Get all stake accounts** - Fetch all staking accounts on-chain
206
+ 2. **Get single stake** - Fetch details of a specific stake account
207
+ 3. **Get multiple stakes** - Fetch multiple stake accounts by address
208
+ 4. **Analyze distribution** - Calculate staking statistics (total, average, largest)
209
+
210
+ ### Running the Example
211
+
212
+ ```bash
213
+ # Navigate to the examples/node directory
214
+ cd examples/node
215
+
216
+ # Install dependencies
217
+ npm install
218
+
219
+ # Run the stake-program example
220
+ npm run stake-program
221
+ ```
222
+
223
+ ### Example Usage
224
+
225
+ ```typescript
226
+ import { NosanaClient, NosanaNetwork } from '@nosana/kit';
227
+
228
+ const client = new NosanaClient(NosanaNetwork.MAINNET);
229
+
230
+ // Get all stake accounts
231
+ const allStakes = await client.stake.all();
232
+ console.log(`Found ${allStakes.length} stake accounts`);
233
+
234
+ // Get single stake account
235
+ const stake = await client.stake.get('stake-account-address');
236
+ console.log(`Staked amount: ${stake.amount}`);
237
+ console.log(`xNOS tokens: ${stake.xnos}`);
238
+
239
+ // Analyze staking distribution
240
+ const totalStaked = allStakes.reduce((sum, s) => sum + s.amount, 0);
241
+ const averageStake = totalStaked / allStakes.length;
242
+ console.log(`Total staked: ${totalStaked.toLocaleString()}`);
243
+ console.log(`Average stake: ${averageStake.toLocaleString()}`);
244
+ ```
245
+
131
246
  ## Other Examples
132
247
 
133
248
  - `retrieve.ts` - Example of retrieving job data
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Example: Using the NosService to interact with NOS token accounts
3
+ *
4
+ * This example demonstrates how to use the NosService to:
5
+ * 1. Get all NOS token holders
6
+ * 2. Get a specific token account for an address
7
+ * 3. Get the NOS balance for an address
8
+ */
9
+
10
+ import { NosanaClient, NosanaNetwork } from '@nosana/kit';
11
+
12
+ async function main() {
13
+ // Initialize the Nosana client (use MAINNET for real data)
14
+ const client = new NosanaClient(NosanaNetwork.MAINNET);
15
+
16
+ // Example 1: Get all NOS token holders (excludes zero balance by default)
17
+ console.log('Fetching all NOS token holders...');
18
+ try {
19
+ const holders = await client.nos.getAllTokenHolders();
20
+
21
+ console.log(`Found ${holders.length} NOS token holders (with non-zero balances)`);
22
+
23
+ // Display first 5 holders
24
+ holders.slice(0, 5).forEach((holder, index) => {
25
+ console.log(`\nHolder ${index + 1}:`);
26
+ console.log(` Token Account: ${holder.pubkey}`);
27
+ console.log(` Owner: ${holder.owner}`);
28
+ console.log(` Balance: ${holder.uiAmount} NOS`);
29
+ console.log(` Raw Amount: ${holder.amount.toString()}`);
30
+ console.log(` Decimals: ${holder.decimals}`);
31
+ });
32
+
33
+ // Include zero balance accounts
34
+ console.log('\nFetching all accounts including zero balances...');
35
+ const allAccounts = await client.token.getAllTokenHolders({ includeZeroBalance: true });
36
+ console.log(`Total accounts (including zero balance): ${allAccounts.length}`);
37
+ console.log(`Accounts with zero balance: ${allAccounts.length - holders.length}`);
38
+
39
+ // Exclude PDA accounts (smart contract-owned accounts)
40
+ console.log('\nFetching user-owned accounts only (excluding PDAs)...');
41
+ const userAccounts = await client.token.getAllTokenHolders({ excludePdaAccounts: true });
42
+ console.log(`User-owned accounts: ${userAccounts.length}`);
43
+ console.log(`PDA accounts: ${holders.length - userAccounts.length}`);
44
+ } catch (error) {
45
+ console.error('Error fetching token holders:', error);
46
+ }
47
+
48
+ // Example 2: Get token account for a specific address
49
+ const ownerAddress = 'YourWalletAddressHere';
50
+
51
+ console.log(`\nFetching NOS token account for ${ownerAddress}...`);
52
+ try {
53
+ const account = await client.nos.getTokenAccountForAddress(ownerAddress);
54
+
55
+ if (account) {
56
+ console.log('Token Account found:');
57
+ console.log(` Account Address: ${account.pubkey}`);
58
+ console.log(` Owner: ${account.owner}`);
59
+ console.log(` Mint: ${account.mint}`);
60
+ console.log(` Balance: ${account.uiAmount} NOS`);
61
+ console.log(` Raw Amount: ${account.amount.toString()}`);
62
+ console.log(` Decimals: ${account.decimals}`);
63
+ } else {
64
+ console.log('No NOS token account found for this address');
65
+ }
66
+ } catch (error) {
67
+ console.error('Error fetching token account:', error);
68
+ }
69
+
70
+ // Example 3: Get just the balance (convenience method)
71
+ console.log(`\nFetching NOS balance for ${ownerAddress}...`);
72
+ try {
73
+ const balance = await client.nos.getBalance(ownerAddress);
74
+ console.log(`Balance: ${balance} NOS`);
75
+ } catch (error) {
76
+ console.error('Error fetching balance:', error);
77
+ }
78
+
79
+ // Example 4: Working with multiple addresses
80
+ const addresses = [
81
+ 'Address1Here',
82
+ 'Address2Here',
83
+ 'Address3Here',
84
+ ];
85
+
86
+ console.log('\nFetching balances for multiple addresses...');
87
+ for (const addr of addresses) {
88
+ try {
89
+ const balance = await client.nos.getBalance(addr);
90
+ console.log(`${addr}: ${balance} NOS`);
91
+ } catch (error) {
92
+ console.error(`Error fetching balance for ${addr}:`, error);
93
+ }
94
+ }
95
+
96
+ // Example 5: Filter holders by minimum balance
97
+ console.log('\nFinding holders with at least 1000 NOS...');
98
+ try {
99
+ const holders = await client.nos.getAllTokenHolders();
100
+ const largeHolders = holders.filter(holder => holder.uiAmount >= 1000);
101
+
102
+ console.log(`Found ${largeHolders.length} holders with >= 1000 NOS`);
103
+
104
+ // Sort by balance descending
105
+ const sorted = largeHolders.sort((a, b) => b.uiAmount - a.uiAmount);
106
+
107
+ // Display top 10
108
+ sorted.slice(0, 10).forEach((holder, index) => {
109
+ console.log(`${index + 1}. ${holder.owner}: ${holder.uiAmount.toLocaleString()} NOS`);
110
+ });
111
+ } catch (error) {
112
+ console.error('Error analyzing holders:', error);
113
+ }
114
+ }
115
+
116
+ main().catch(console.error);
117
+
@@ -6,7 +6,9 @@
6
6
  "scripts": {
7
7
  "retrieve": "tsx retrieve.ts",
8
8
  "monitor": "tsx monitor.ts",
9
- "set-wallet": "tsx set-wallet.ts"
9
+ "set-wallet": "tsx set-wallet.ts",
10
+ "nos-service": "tsx nos-service.ts",
11
+ "stake-program": "tsx stake-program.ts"
10
12
  },
11
13
  "dependencies": {
12
14
  "@nosana/kit": "file:../.."
@@ -2,11 +2,11 @@ import { address, NosanaClient, NosanaNetwork } from '@nosana/kit';
2
2
 
3
3
  async function main() {
4
4
  // Initialize the client with devnet for testing
5
- const client = new NosanaClient(NosanaNetwork.MAINNET);
5
+ const client = new NosanaClient(NosanaNetwork.DEVNET);
6
6
 
7
7
  try {
8
8
  // Example: Get job details
9
- const jobAddress = 'BwBURHTRMM3Ckzo2Dzmw99hv6gV8Ve12b6iw4sm9qeyR'; // Replace with actual job address
9
+ const jobAddress = 'FMbwxyhhAwXzixRqoSFhYzbx6RAQE4kgoTCdHXkNQ6AR'; // Replace with actual job address
10
10
  const jobDetails = await client.jobs.get(address(jobAddress));
11
11
  console.log('Job details:', jobDetails);
12
12
 
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Example: Using the StakeProgram to interact with staking accounts
3
+ *
4
+ * This example demonstrates how to use the StakeProgram to:
5
+ * 1. Get all stake accounts
6
+ * 2. Get stake accounts for a specific authority
7
+ * 3. Get a single stake account by address
8
+ */
9
+
10
+ import { NosanaClient, NosanaNetwork } from '@nosana/kit';
11
+
12
+ async function main() {
13
+ // Initialize the client
14
+ const client = new NosanaClient(NosanaNetwork.MAINNET);
15
+
16
+ console.log('=== Stake Program Example ===\n');
17
+
18
+ try {
19
+ // Example 1: Get all stake accounts
20
+ console.log('1. Fetching all stake accounts...');
21
+ const allStakes = await client.stake.all();
22
+ console.log(` Found ${allStakes.length} stake accounts`);
23
+
24
+ if (allStakes.length > 0) {
25
+ const firstStake = allStakes[0];
26
+ console.log(' First stake account:');
27
+ console.log(` - Address: ${firstStake.address}`);
28
+ console.log(` - Authority: ${firstStake.authority}`);
29
+ console.log(` - Amount: ${firstStake.amount}`);
30
+ console.log(` - xNOS: ${firstStake.xnos}`);
31
+ console.log(` - Duration: ${firstStake.duration}`);
32
+ console.log(` - Time Unstake: ${firstStake.timeUnstake}`);
33
+ console.log(` - Vault: ${firstStake.vault}`);
34
+ console.log(` - Vault Bump: ${firstStake.vaultBump}`);
35
+ }
36
+ console.log();
37
+
38
+ // Example 2: Get a single stake account (if you know a stake account address)
39
+ if (allStakes.length > 0) {
40
+ console.log('2. Fetching single stake account...');
41
+ const stakeAddress = allStakes[0].address;
42
+ const stake = await client.stake.get(stakeAddress);
43
+ console.log(` Retrieved stake account: ${stake.address}`);
44
+ console.log(` - Authority: ${stake.authority}`);
45
+ console.log(` - Staked Amount: ${stake.amount}`);
46
+ console.log(` - xNOS Tokens: ${stake.xnos}`);
47
+ console.log();
48
+ }
49
+
50
+ // Example 3: Get multiple stake accounts by address
51
+ if (allStakes.length >= 2) {
52
+ console.log('3. Fetching multiple stake accounts...');
53
+ const addresses = allStakes.slice(0, 2).map(s => s.address);
54
+ const stakes = await client.stake.multiple(addresses);
55
+ console.log(` Retrieved ${stakes.length} stake accounts`);
56
+ stakes.forEach((stake, i) => {
57
+ console.log(` Stake ${i + 1}:`);
58
+ console.log(` - Address: ${stake.address}`);
59
+ console.log(` - Authority: ${stake.authority}`);
60
+ console.log(` - Amount: ${stake.amount}`);
61
+ });
62
+ console.log();
63
+ }
64
+
65
+ // Example 4: Analyze staking distribution
66
+ console.log('4. Analyzing staking distribution...');
67
+ const totalStaked = allStakes.reduce((sum, stake) => sum + stake.amount, 0);
68
+ const averageStake = allStakes.length > 0 ? totalStaked / allStakes.length : 0;
69
+ const largestStake = allStakes.reduce((max, stake) =>
70
+ stake.amount > max ? stake.amount : max, 0
71
+ );
72
+
73
+ console.log(` Total staked: ${totalStaked}`);
74
+ console.log(` Average stake: ${averageStake.toFixed(2)}`);
75
+ console.log(` Largest stake: ${largestStake}`);
76
+ console.log(` Number of stakers: ${allStakes.length}`);
77
+
78
+ } catch (error) {
79
+ console.error('Error:', error);
80
+ }
81
+ }
82
+
83
+ main();
84
+
package/package.json CHANGED
@@ -1,17 +1,21 @@
1
1
  {
2
2
  "name": "@nosana/kit",
3
- "version": "0.1.6",
3
+ "version": "1.0.2",
4
4
  "description": "Nosana KIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "scripts": {
9
9
  "build": "tsc",
10
- "test": "jest",
11
- "generate-clients": "ts-node scripts/generate-clients.ts",
12
- "lint": "eslint . --ext .ts",
10
+ "test": "vitest run",
11
+ "test:coverage": "vitest run --coverage",
12
+ "test:watch": "vitest",
13
+ "generate-clients": "npx tsx scripts/generate-clients.ts",
14
+ "lint": "eslint .",
15
+ "lint:fix": "eslint . --fix",
13
16
  "format": "prettier --write \"**/*.ts\"",
14
- "publish:public": "npm version patch && npm run build && npm publish --access public"
17
+ "prettier": "prettier --check \"**/*.ts\"",
18
+ "publish:public": "npm run build && npm publish --access public"
15
19
  },
16
20
  "keywords": [
17
21
  "nosana",
@@ -24,22 +28,23 @@
24
28
  "license": "MIT",
25
29
  "dependencies": {
26
30
  "@solana-program/token": "^0.5.1",
31
+ "axios": "^1.6.0",
27
32
  "bs58": "^6.0.0",
28
33
  "buffer": "^6.0.3",
34
+ "form-data": "^4.0.0",
29
35
  "gill": "^0.9.0"
30
36
  },
31
37
  "devDependencies": {
32
38
  "@codama/nodes-from-anchor": "^1.2.0",
33
39
  "@codama/renderers-js": "^1.2.14",
34
- "@types/jest": "^29.5.12",
35
40
  "@types/node": "^20.11.24",
36
41
  "@typescript-eslint/eslint-plugin": "^7.1.0",
37
42
  "@typescript-eslint/parser": "^7.1.0",
38
43
  "codama": "^1.3.0",
39
44
  "eslint": "^8.57.0",
40
- "jest": "^29.7.0",
45
+ "vitest": "^1.6.0",
46
+ "@vitest/coverage-v8": "^1.6.0",
41
47
  "prettier": "^3.2.5",
42
- "ts-jest": "^29.1.2",
43
48
  "ts-node": "^10.9.2",
44
49
  "typescript": "^5.3.3"
45
50
  },