@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
|
@@ -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
|
|
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({
|
|
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
|
|
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
|
|
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
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -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,
|
|
19
|
+
export declare function convertBigIntToNumber<T extends Record<string, unknown>>(obj: T): ConvertBigIntToNumber<T>;
|
|
20
|
+
export * from './walletConverter.js';
|
package/dist/utils/index.js
CHANGED
|
@@ -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
|
+
}
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
},
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
package/examples/node/README.md
CHANGED
|
@@ -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.
|
|
5
|
+
const client = new NosanaClient(NosanaNetwork.DEVNET);
|
|
6
6
|
|
|
7
7
|
try {
|
|
8
8
|
// Example: Get job details
|
|
9
|
-
const jobAddress = '
|
|
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.
|
|
3
|
+
"version": "1.0.3",
|
|
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": "
|
|
11
|
-
"
|
|
12
|
-
"
|
|
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
|
-
"
|
|
17
|
+
"prettier": "prettier --check \"**/*.ts\"",
|
|
18
|
+
"publish:public": "npm run build && npm publish --access public"
|
|
15
19
|
},
|
|
16
20
|
"keywords": [
|
|
17
21
|
"nosana",
|
|
@@ -33,15 +37,14 @@
|
|
|
33
37
|
"devDependencies": {
|
|
34
38
|
"@codama/nodes-from-anchor": "^1.2.0",
|
|
35
39
|
"@codama/renderers-js": "^1.2.14",
|
|
36
|
-
"@types/jest": "^29.5.12",
|
|
37
40
|
"@types/node": "^20.11.24",
|
|
38
41
|
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
|
39
42
|
"@typescript-eslint/parser": "^7.1.0",
|
|
40
43
|
"codama": "^1.3.0",
|
|
41
44
|
"eslint": "^8.57.0",
|
|
42
|
-
"
|
|
45
|
+
"vitest": "^1.6.0",
|
|
46
|
+
"@vitest/coverage-v8": "^1.6.0",
|
|
43
47
|
"prettier": "^3.2.5",
|
|
44
|
-
"ts-jest": "^29.1.2",
|
|
45
48
|
"ts-node": "^10.9.2",
|
|
46
49
|
"typescript": "^5.3.3"
|
|
47
50
|
},
|