@nosana/kit 0.1.5 → 0.1.7

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.
@@ -38,4 +38,6 @@ export interface ClientConfig {
38
38
  export interface PartialClientConfig {
39
39
  solana?: Partial<SolanaConfig>;
40
40
  wallet?: WalletConfig;
41
+ ipfs?: Partial<IpfsConfig>;
42
+ logLevel?: NosanaLogLevel;
41
43
  }
@@ -11,6 +11,10 @@ export const mergeConfigs = (defaultConfig, customConfig) => {
11
11
  ...defaultConfig.solana,
12
12
  ...customConfig.solana,
13
13
  },
14
+ ipfs: {
15
+ ...defaultConfig.ipfs,
16
+ ...customConfig.ipfs,
17
+ },
14
18
  };
15
19
  };
16
20
  export const getNosanaConfig = (network = NosanaNetwork.MAINNET, config) => {
@@ -27,4 +31,9 @@ export const getNosanaConfig = (network = NosanaNetwork.MAINNET, config) => {
27
31
  // solana: {
28
32
  // rpcEndpoint: 'your-custom-rpc-endpoint-url',
29
33
  // },
34
+ // ipfs: {
35
+ // jwt: 'your-custom-jwt-token',
36
+ // gateway: 'https://your-custom-gateway.com/ipfs/',
37
+ // },
38
+ // logLevel: NosanaLogLevel.DEBUG,
30
39
  // });
package/dist/index.d.ts CHANGED
@@ -2,11 +2,13 @@ import { ClientConfig, NosanaNetwork, PartialClientConfig, WalletConfig } from '
2
2
  import { Logger } from './logger/Logger.js';
3
3
  import { JobsProgram } from './programs/JobsProgram.js';
4
4
  import { SolanaUtils } from './solana/SolanaUtils.js';
5
+ import { IPFS } from './ipfs/IPFS.js';
5
6
  import { KeyPairSigner } from 'gill';
6
7
  export declare class NosanaClient {
7
8
  readonly config: ClientConfig;
8
9
  readonly jobs: JobsProgram;
9
10
  readonly solana: SolanaUtils;
11
+ readonly ipfs: IPFS;
10
12
  readonly logger: Logger;
11
13
  wallet: KeyPairSigner | undefined;
12
14
  constructor(network?: NosanaNetwork, customConfig?: PartialClientConfig);
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { getNosanaConfig, NosanaNetwork } from './config/index.js';
6
6
  import { Logger } from './logger/Logger.js';
7
7
  import { JobsProgram } from './programs/JobsProgram.js';
8
8
  import { SolanaUtils } from './solana/SolanaUtils.js';
9
+ import { IPFS } from './ipfs/IPFS.js';
9
10
  import { createKeyPairSignerFromBytes } from 'gill';
10
11
  import { NosanaError, ErrorCodes } from './errors/NosanaError.js';
11
12
  import bs58 from 'bs58';
@@ -18,6 +19,7 @@ export class NosanaClient {
18
19
  this.jobs = new JobsProgram(this);
19
20
  this.logger = Logger.getInstance();
20
21
  this.solana = new SolanaUtils(this);
22
+ this.ipfs = new IPFS(this.config.ipfs);
21
23
  }
22
24
  async setWallet(wallet) {
23
25
  try {
@@ -1,19 +1,50 @@
1
+ import { AxiosRequestConfig } from 'axios';
1
2
  import { ReadonlyUint8Array } from 'gill';
3
+ import type { IpfsConfig } from '../config/types.js';
2
4
  /**
3
5
  * Class to interact with Pinata Cloud
4
6
  * https://www.pinata.cloud/
5
7
  */
6
8
  export declare class IPFS {
9
+ private api;
10
+ config: IpfsConfig;
11
+ constructor(config: IpfsConfig);
7
12
  /**
8
13
  * Convert the ipfs bytes from a solana job to a CID
9
14
  * It prepends the 0x1220 (18,32) to make it 34 bytes and Base58 encodes it.
10
15
  * This result is IPFS addressable.
11
16
  */
12
- static solHashToIpfsHash(hashArray: ReadonlyUint8Array): string;
17
+ static solHashToIpfsHash(hashArray: ReadonlyUint8Array | Array<number>): string | null;
13
18
  /**
14
19
  * Converts IPFS hash to byte array needed to submit results
15
20
  * @param hash IPFS hash
16
21
  * @returns Array<number>
17
22
  */
18
- static IpfsHashToByteArray(hash: string): Uint8Array;
23
+ static IpfsHashToByteArray(hash: string): Array<number>;
24
+ /**
25
+ * Retrieve data from IPFS using the configured gateway
26
+ * @param hash IPFS hash string or byte array
27
+ * @param options Additional axios request options
28
+ * @returns The retrieved data
29
+ */
30
+ retrieve(hash: string | Array<number>, options?: AxiosRequestConfig): Promise<any>;
31
+ /**
32
+ * Function to pin data into Pinata Cloud
33
+ * @param data Object to pin into IPFS as JSON
34
+ * @returns The IPFS hash of the pinned data
35
+ */
36
+ pin(data: object): Promise<string>;
37
+ /**
38
+ * Function to pin a file into Pinata Cloud
39
+ * @param filePath Path to the file to pin
40
+ * @returns The IPFS hash of the pinned file
41
+ */
42
+ pinFile(filePath: string): Promise<string>;
43
+ /**
44
+ * Function to pin a file from buffer/blob into Pinata Cloud
45
+ * @param fileBuffer Buffer or Blob containing the file data
46
+ * @param fileName Name of the file
47
+ * @returns The IPFS hash of the pinned file
48
+ */
49
+ pinFileFromBuffer(fileBuffer: Buffer | Blob, fileName: string): Promise<string>;
19
50
  }
package/dist/ipfs/IPFS.js CHANGED
@@ -1,9 +1,41 @@
1
1
  import bs58 from 'bs58';
2
+ import axios, { AxiosHeaders } from 'axios';
3
+ // Import form-data dynamically for Node.js environments
4
+ let FormData;
5
+ let fs;
6
+ // Dynamically import Node.js-specific modules
7
+ const loadNodeModules = async () => {
8
+ if (typeof window === 'undefined') {
9
+ try {
10
+ const formDataModule = await import('form-data');
11
+ FormData = formDataModule.default;
12
+ fs = await import('fs');
13
+ }
14
+ catch (error) {
15
+ console.warn('Node.js modules not available for file operations');
16
+ }
17
+ }
18
+ else {
19
+ // Use browser FormData
20
+ FormData = window.FormData;
21
+ }
22
+ };
2
23
  /**
3
24
  * Class to interact with Pinata Cloud
4
25
  * https://www.pinata.cloud/
5
26
  */
6
27
  export class IPFS {
28
+ constructor(config) {
29
+ this.config = config;
30
+ const headers = new AxiosHeaders();
31
+ if (this.config.jwt) {
32
+ headers.set('Authorization', `Bearer ${this.config.jwt}`);
33
+ }
34
+ this.api = axios.create({
35
+ baseURL: this.config.api,
36
+ headers,
37
+ });
38
+ }
7
39
  /**
8
40
  * Convert the ipfs bytes from a solana job to a CID
9
41
  * It prepends the 0x1220 (18,32) to make it 34 bytes and Base58 encodes it.
@@ -11,18 +43,23 @@ export class IPFS {
11
43
  */
12
44
  static solHashToIpfsHash(hashArray) {
13
45
  let finalArray;
14
- if (hashArray.length === 32) {
46
+ const inputArray = Array.isArray(hashArray) ? hashArray : Array.from(hashArray);
47
+ if (inputArray.length === 32) {
15
48
  // Create a new array with the prepended bytes [18, 32] + original array
16
49
  finalArray = new Uint8Array(34);
17
50
  finalArray[0] = 18;
18
51
  finalArray[1] = 32;
19
- finalArray.set(hashArray, 2);
52
+ finalArray.set(inputArray, 2);
20
53
  }
21
54
  else {
22
55
  // Use the array as-is if it's not 32 bytes
23
- finalArray = new Uint8Array(hashArray);
56
+ finalArray = new Uint8Array(inputArray);
24
57
  }
25
- return bs58.encode(Buffer.from(finalArray));
58
+ const hash = bs58.encode(Buffer.from(finalArray));
59
+ if (hash === 'QmNLei78zWmzUdbeRB3CiUfAizWUrbeeZh5K1rhAQKCh51') {
60
+ return null;
61
+ }
62
+ return hash;
26
63
  }
27
64
  /**
28
65
  * Converts IPFS hash to byte array needed to submit results
@@ -31,10 +68,79 @@ export class IPFS {
31
68
  */
32
69
  static IpfsHashToByteArray(hash) {
33
70
  if (hash.length === 34) {
34
- return new Uint8Array([...bs58.decode(hash).subarray(2)]);
71
+ return [...bs58.decode(hash).subarray(2)];
35
72
  }
36
73
  else {
37
- return new Uint8Array([...bs58.decode(hash)]);
74
+ return [...bs58.decode(hash)];
75
+ }
76
+ }
77
+ /**
78
+ * Retrieve data from IPFS using the configured gateway
79
+ * @param hash IPFS hash string or byte array
80
+ * @param options Additional axios request options
81
+ * @returns The retrieved data
82
+ */
83
+ async retrieve(hash, options = {}) {
84
+ if (typeof hash !== 'string') {
85
+ const convertedHash = IPFS.solHashToIpfsHash(hash);
86
+ if (!convertedHash) {
87
+ throw new Error('Invalid hash provided');
88
+ }
89
+ hash = convertedHash;
90
+ }
91
+ const response = await axios.get(this.config.gateway + hash, options);
92
+ return response.data;
93
+ }
94
+ /**
95
+ * Function to pin data into Pinata Cloud
96
+ * @param data Object to pin into IPFS as JSON
97
+ * @returns The IPFS hash of the pinned data
98
+ */
99
+ async pin(data) {
100
+ const response = await this.api.post('/pinning/pinJSONToIPFS', data);
101
+ return response.data.IpfsHash;
102
+ }
103
+ /**
104
+ * Function to pin a file into Pinata Cloud
105
+ * @param filePath Path to the file to pin
106
+ * @returns The IPFS hash of the pinned file
107
+ */
108
+ async pinFile(filePath) {
109
+ // Ensure Node.js modules are loaded
110
+ await loadNodeModules();
111
+ if (!FormData || !fs) {
112
+ throw new Error('File operations are not supported in this environment');
113
+ }
114
+ const data = new FormData();
115
+ data.append('file', fs.createReadStream(filePath));
116
+ const response = await this.api.post('/pinning/pinFileToIPFS', data, {
117
+ headers: {
118
+ 'Content-Type': `multipart/form-data; boundary=${data.getBoundary()}`,
119
+ Authorization: `Bearer ${this.config.jwt}`,
120
+ },
121
+ });
122
+ return response.data.IpfsHash;
123
+ }
124
+ /**
125
+ * Function to pin a file from buffer/blob into Pinata Cloud
126
+ * @param fileBuffer Buffer or Blob containing the file data
127
+ * @param fileName Name of the file
128
+ * @returns The IPFS hash of the pinned file
129
+ */
130
+ async pinFileFromBuffer(fileBuffer, fileName) {
131
+ // Ensure FormData is available
132
+ await loadNodeModules();
133
+ if (!FormData) {
134
+ throw new Error('FormData is not available in this environment');
38
135
  }
136
+ const data = new FormData();
137
+ data.append('file', fileBuffer, fileName);
138
+ const response = await this.api.post('/pinning/pinFileToIPFS', data, {
139
+ headers: {
140
+ 'Content-Type': `multipart/form-data${data.getBoundary ? `; boundary=${data.getBoundary()}` : ''}`,
141
+ Authorization: `Bearer ${this.config.jwt}`,
142
+ },
143
+ });
144
+ return response.data.IpfsHash;
39
145
  }
40
146
  }
@@ -9,7 +9,7 @@ export type ConvertBigIntToNumber<T> = {
9
9
  * Type helper to convert bigint to number and ReadonlyUint8Array to string
10
10
  */
11
11
  export type ConvertTypesForDb<T> = {
12
- [K in keyof T]: T[K] extends bigint ? number : T[K] extends ReadonlyUint8Array ? string : T[K];
12
+ [K in keyof T]: T[K] extends bigint ? number : T[K] extends ReadonlyUint8Array ? string | null : T[K];
13
13
  };
14
14
  /**
15
15
  * Helper function to convert bigint values to numbers in an object
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nosana/kit",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Nosana KIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -24,8 +24,10 @@
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "@solana-program/token": "^0.5.1",
27
+ "axios": "^1.6.0",
27
28
  "bs58": "^6.0.0",
28
29
  "buffer": "^6.0.3",
30
+ "form-data": "^4.0.0",
29
31
  "gill": "^0.9.0"
30
32
  },
31
33
  "devDependencies": {