0g-orbit 0.2.1 → 0.2.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +21 -0
  3. package/README.md +151 -0
  4. package/dist/cli/cli.js +1 -1
  5. package/dist/storage.d.ts.map +1 -1
  6. package/dist/storage.js +23 -1
  7. package/dist/storage.js.map +1 -1
  8. package/package.json +28 -4
  9. package/examples/ai-chatbot/index.ts +0 -74
  10. package/examples/model-registry/index.ts +0 -137
  11. package/examples/quick-start/index.ts +0 -65
  12. package/packages/cli/package.json +0 -30
  13. package/packages/cli/src/cli.ts +0 -69
  14. package/packages/cli/src/commands/account.ts +0 -29
  15. package/packages/cli/src/commands/inference.ts +0 -103
  16. package/packages/cli/src/commands/init.ts +0 -71
  17. package/packages/cli/src/commands/storage.ts +0 -91
  18. package/packages/cli/src/utils.ts +0 -21
  19. package/packages/cli/tsconfig.json +0 -8
  20. package/packages/core/package.json +0 -35
  21. package/packages/core/src/errors.test.ts +0 -99
  22. package/packages/core/src/errors.ts +0 -79
  23. package/packages/core/src/index.ts +0 -37
  24. package/packages/core/src/inference.ts +0 -256
  25. package/packages/core/src/networks.test.ts +0 -62
  26. package/packages/core/src/networks.ts +0 -62
  27. package/packages/core/src/orbit.test.ts +0 -153
  28. package/packages/core/src/orbit.ts +0 -159
  29. package/packages/core/src/retry.test.ts +0 -99
  30. package/packages/core/src/retry.ts +0 -99
  31. package/packages/core/src/storage.test.ts +0 -199
  32. package/packages/core/src/storage.ts +0 -158
  33. package/packages/core/src/types.ts +0 -85
  34. package/packages/core/tsconfig.json +0 -8
  35. package/packages/core/vitest.config.ts +0 -7
  36. package/src/cli/cli.ts +0 -95
  37. package/src/cli/commands/account.ts +0 -29
  38. package/src/cli/commands/fine-tuning.ts +0 -169
  39. package/src/cli/commands/inference.ts +0 -103
  40. package/src/cli/commands/init.ts +0 -71
  41. package/src/cli/commands/storage.ts +0 -91
  42. package/src/cli/utils.ts +0 -21
  43. package/src/errors.test.ts +0 -99
  44. package/src/errors.ts +0 -90
  45. package/src/fine-tuning.test.ts +0 -299
  46. package/src/fine-tuning.ts +0 -330
  47. package/src/index.ts +0 -45
  48. package/src/inference.ts +0 -256
  49. package/src/networks.test.ts +0 -62
  50. package/src/networks.ts +0 -62
  51. package/src/orbit.test.ts +0 -153
  52. package/src/orbit.ts +0 -204
  53. package/src/retry.test.ts +0 -99
  54. package/src/retry.ts +0 -99
  55. package/src/storage.test.ts +0 -199
  56. package/src/storage.ts +0 -158
  57. package/src/types.ts +0 -157
  58. package/tsconfig.json +0 -20
  59. package/vitest.config.ts +0 -7
@@ -1,199 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
- import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs'
3
- import { tmpdir } from 'node:os'
4
- import { join } from 'node:path'
5
-
6
- // Mock the 0G SDK
7
- vi.mock('@0gfoundation/0g-ts-sdk', () => {
8
- const mockClose = vi.fn()
9
- const mockMerkleTree = vi.fn().mockResolvedValue([{ rootHash: '0xabc' }, null])
10
-
11
- return {
12
- ZgFile: {
13
- fromFilePath: vi.fn().mockResolvedValue({
14
- merkleTree: mockMerkleTree,
15
- close: mockClose,
16
- size: () => 100,
17
- }),
18
- },
19
- Indexer: vi.fn().mockImplementation(() => ({
20
- upload: vi.fn().mockResolvedValue([
21
- { txHash: '0xtx123', rootHash: '0xroot456' },
22
- null,
23
- ]),
24
- download: vi.fn().mockResolvedValue(null),
25
- })),
26
- }
27
- })
28
-
29
- import { StorageClient } from './storage.js'
30
- import { StorageError } from './errors.js'
31
- import { NETWORKS } from './networks.js'
32
- import { Indexer, ZgFile } from '@0gfoundation/0g-ts-sdk'
33
-
34
- const mockSigner = { address: '0x123' } as any
35
- const testNetwork = { ...NETWORKS.testnet }
36
-
37
- describe('StorageClient', () => {
38
- beforeEach(() => {
39
- vi.clearAllMocks()
40
- })
41
-
42
- describe('store', () => {
43
- it('uploads a file and returns root + tx hash', async () => {
44
- const client = new StorageClient(testNetwork, mockSigner)
45
- const result = await client.store('/tmp/test.txt')
46
- expect(result.root).toBe('0xroot456')
47
- expect(result.txHash).toBe('0xtx123')
48
- })
49
-
50
- it('passes tags and replicas to upload', async () => {
51
- const client = new StorageClient(testNetwork, mockSigner)
52
- await client.store('/tmp/test.txt', { tags: '0xbeef', replicas: 3 })
53
-
54
- const indexerInstance = (Indexer as any).mock.results[0].value
55
- expect(indexerInstance.upload).toHaveBeenCalledWith(
56
- expect.anything(),
57
- testNetwork.rpcUrl,
58
- expect.anything(),
59
- expect.objectContaining({ tags: '0xbeef', expectedReplica: 3 }),
60
- expect.objectContaining({ Retries: 5, Interval: 3 })
61
- )
62
- })
63
-
64
- it('always passes gas retry options', async () => {
65
- const client = new StorageClient(testNetwork, mockSigner)
66
- await client.store('/tmp/test.txt')
67
-
68
- const indexerInstance = (Indexer as any).mock.results[0].value
69
- const retryOpts = indexerInstance.upload.mock.calls[0][4]
70
- expect(retryOpts).toBeDefined()
71
- expect(retryOpts.Retries).toBe(5)
72
- expect(retryOpts.MaxGasPrice).toBeGreaterThan(0)
73
- })
74
-
75
- it('throws StorageError with suggestion on merkle failure', async () => {
76
- const mockFile = {
77
- merkleTree: vi.fn().mockResolvedValue([null, new Error('corrupt file')]),
78
- close: vi.fn(),
79
- }
80
- ;(ZgFile.fromFilePath as any).mockResolvedValueOnce(mockFile)
81
-
82
- const client = new StorageClient(testNetwork, mockSigner)
83
- try {
84
- await client.store('/tmp/bad.txt')
85
- expect.unreachable('should have thrown')
86
- } catch (err) {
87
- expect(err).toBeInstanceOf(StorageError)
88
- expect((err as StorageError).suggestion).toContain('file exists')
89
- }
90
- })
91
-
92
- it('throws StorageError with gas suggestion on underpriced', async () => {
93
- const indexerMock = {
94
- upload: vi.fn().mockResolvedValue([
95
- { txHash: '', rootHash: '' },
96
- new Error('transaction underpriced'),
97
- ]),
98
- download: vi.fn(),
99
- }
100
- ;(Indexer as any).mockImplementationOnce(() => indexerMock)
101
-
102
- const client = new StorageClient(testNetwork, mockSigner)
103
- try {
104
- await client.store('/tmp/test.txt')
105
- expect.unreachable('should have thrown')
106
- } catch (err) {
107
- expect(err).toBeInstanceOf(StorageError)
108
- expect((err as StorageError).suggestion).toContain('maxGasPrice')
109
- }
110
- })
111
-
112
- it('always closes the file even on error', async () => {
113
- const mockClose = vi.fn()
114
- const mockFile = {
115
- merkleTree: vi.fn().mockRejectedValue(new Error('boom')),
116
- close: mockClose,
117
- }
118
- ;(ZgFile.fromFilePath as any).mockResolvedValueOnce(mockFile)
119
-
120
- const client = new StorageClient(testNetwork, mockSigner)
121
- await client.store('/tmp/test.txt').catch(() => {})
122
- expect(mockClose).toHaveBeenCalled()
123
- })
124
- })
125
-
126
- describe('storeData', () => {
127
- it('stores a string by writing to temp file', async () => {
128
- const client = new StorageClient(testNetwork, mockSigner)
129
- const result = await client.storeData('hello world')
130
- expect(result.root).toBe('0xroot456')
131
- expect(result.txHash).toBe('0xtx123')
132
-
133
- // Verify ZgFile.fromFilePath was called with a temp path
134
- const calledPath = (ZgFile.fromFilePath as any).mock.calls[0][0]
135
- expect(calledPath).toContain('orbit-')
136
- })
137
-
138
- it('stores a Buffer', async () => {
139
- const client = new StorageClient(testNetwork, mockSigner)
140
- const result = await client.storeData(Buffer.from('binary data'))
141
- expect(result.root).toBe('0xroot456')
142
- })
143
-
144
- it('stores a Uint8Array', async () => {
145
- const client = new StorageClient(testNetwork, mockSigner)
146
- const result = await client.storeData(new Uint8Array([1, 2, 3]))
147
- expect(result.root).toBe('0xroot456')
148
- })
149
-
150
- it('cleans up temp file after upload', async () => {
151
- const client = new StorageClient(testNetwork, mockSigner)
152
- await client.storeData('cleanup test')
153
-
154
- const calledPath = (ZgFile.fromFilePath as any).mock.calls[0][0]
155
- // Temp file should have been deleted
156
- expect(existsSync(calledPath)).toBe(false)
157
- })
158
-
159
- it('cleans up temp file even on error', async () => {
160
- const mockFile = {
161
- merkleTree: vi.fn().mockResolvedValue([null, new Error('fail')]),
162
- close: vi.fn(),
163
- }
164
- ;(ZgFile.fromFilePath as any).mockResolvedValueOnce(mockFile)
165
-
166
- const client = new StorageClient(testNetwork, mockSigner)
167
- await client.storeData('error cleanup test').catch(() => {})
168
-
169
- const calledPath = (ZgFile.fromFilePath as any).mock.calls[0][0]
170
- expect(existsSync(calledPath)).toBe(false)
171
- })
172
- })
173
-
174
- describe('retrieve', () => {
175
- it('downloads successfully', async () => {
176
- const client = new StorageClient(testNetwork, mockSigner)
177
- await expect(
178
- client.retrieve('0xroot', '/tmp/output.txt')
179
- ).resolves.toBeUndefined()
180
- })
181
-
182
- it('throws StorageError on download failure', async () => {
183
- const indexerMock = {
184
- upload: vi.fn(),
185
- download: vi.fn().mockResolvedValue(new Error('file not found: 404')),
186
- }
187
- ;(Indexer as any).mockImplementationOnce(() => indexerMock)
188
-
189
- const client = new StorageClient(testNetwork, mockSigner)
190
- try {
191
- await client.retrieve('0xbad', '/tmp/out.txt')
192
- expect.unreachable('should have thrown')
193
- } catch (err) {
194
- expect(err).toBeInstanceOf(StorageError)
195
- expect((err as StorageError).suggestion).toContain('root hash')
196
- }
197
- })
198
- })
199
- })
package/src/storage.ts DELETED
@@ -1,158 +0,0 @@
1
- import { Indexer, ZgFile } from '@0gfoundation/0g-ts-sdk'
2
- import type { Signer } from 'ethers'
3
- import { writeFileSync, unlinkSync } from 'node:fs'
4
- import { tmpdir } from 'node:os'
5
- import { join } from 'node:path'
6
- import { randomBytes } from 'node:crypto'
7
- import type { NetworkConfig } from './networks.js'
8
- import type { StoreResult, StoreOptions, RetrieveOptions } from './types.js'
9
- import { StorageError } from './errors.js'
10
- import { withRetry, isTransientError } from './retry.js'
11
-
12
- const DEFAULT_REPLICAS = 1
13
- const DEFAULT_MAX_GAS_PRICE = 25_000_000_000 // 25 gwei — generous ceiling for auto-escalation
14
-
15
- export class StorageClient {
16
- private indexer: Indexer
17
- private rpcUrl: string
18
- private signer: Signer
19
-
20
- constructor(network: NetworkConfig, signer: Signer) {
21
- this.indexer = new Indexer(network.indexerUrl)
22
- this.rpcUrl = network.rpcUrl
23
- this.signer = signer
24
- }
25
-
26
- /**
27
- * Upload a file to 0G Storage by file path.
28
- * Retries transient failures automatically. Gas escalates on each retry.
29
- */
30
- async store(filePath: string, options: StoreOptions = {}): Promise<StoreResult> {
31
- return withRetry(
32
- () => this._store(filePath, options),
33
- {
34
- maxAttempts: 3,
35
- isRetryable: (err) => {
36
- if (!(err instanceof StorageError)) return isTransientError(err)
37
- const msg = err.message.toLowerCase()
38
- return msg.includes('gas') ||
39
- msg.includes('timeout') ||
40
- msg.includes('etimedout') ||
41
- msg.includes('econnrefused') ||
42
- msg.includes('underpriced')
43
- },
44
- }
45
- )
46
- }
47
-
48
- /**
49
- * Upload raw data (string, Buffer, or Uint8Array) to 0G Storage.
50
- * Writes to a temp file, uploads, then cleans up.
51
- */
52
- async storeData(
53
- data: string | Buffer | Uint8Array,
54
- options: StoreOptions = {}
55
- ): Promise<StoreResult> {
56
- const tempPath = join(tmpdir(), `orbit-${randomBytes(8).toString('hex')}`)
57
- try {
58
- writeFileSync(tempPath, data)
59
- return await this.store(tempPath, options)
60
- } finally {
61
- try { unlinkSync(tempPath) } catch { /* ignore cleanup errors */ }
62
- }
63
- }
64
-
65
- private async _store(filePath: string, options: StoreOptions): Promise<StoreResult> {
66
- const file = await ZgFile.fromFilePath(filePath)
67
- try {
68
- const [tree, treeErr] = await file.merkleTree()
69
- if (treeErr || !tree) {
70
- throw new StorageError(
71
- `Failed to compute merkle tree: ${treeErr?.message ?? 'unknown error'}`,
72
- 'Ensure the file exists, is readable, and is not empty.'
73
- )
74
- }
75
-
76
- // Always enable gas auto-escalation via the SDK's built-in retry.
77
- // MaxGasPrice sets the ceiling — the SDK starts from the network estimate
78
- // and bumps 10% on each retry until it hits this cap.
79
- const maxGas = options.maxGasPrice
80
- ? Number(options.maxGasPrice)
81
- : DEFAULT_MAX_GAS_PRICE
82
-
83
- // Cast signer to avoid ESM/CJS ethers type mismatch
84
- const [result, uploadErr] = await this.indexer.upload(
85
- file,
86
- this.rpcUrl,
87
- this.signer as any,
88
- {
89
- tags: options.tags ?? '0x',
90
- expectedReplica: options.replicas ?? DEFAULT_REPLICAS,
91
- },
92
- { Retries: 5, Interval: 3, MaxGasPrice: maxGas }
93
- )
94
-
95
- if (uploadErr) {
96
- const msg = uploadErr.message
97
- let suggestion = 'Check your OG balance and try again.'
98
- if (msg.includes('insufficient funds') || msg.includes('INSUFFICIENT_FUNDS')) {
99
- suggestion = 'Your wallet needs more OG for gas. Get testnet OG from: https://faucet.0g.ai'
100
- } else if (msg.includes('underpriced') || msg.includes('gas required exceeds')) {
101
- suggestion = `Gas auto-escalation hit the ceiling (${maxGas} wei). Try a higher maxGasPrice, e.g. { maxGasPrice: ${maxGas * 2}n }`
102
- } else if (msg.includes('timeout') || msg.includes('ETIMEDOUT')) {
103
- suggestion = 'The RPC endpoint timed out. Try a different RPC URL with the rpcUrl option.'
104
- }
105
- throw new StorageError(`Upload failed: ${msg}`, suggestion)
106
- }
107
-
108
- if ('txHash' in result) {
109
- return {
110
- root: result.rootHash,
111
- txHash: result.txHash,
112
- }
113
- }
114
-
115
- // Fragmented upload — return the first root hash as primary handle
116
- return {
117
- root: result.rootHashes[0],
118
- txHash: result.txHashes[0],
119
- }
120
- } finally {
121
- await file.close()
122
- }
123
- }
124
-
125
- /**
126
- * Download a file from 0G Storage by root hash.
127
- */
128
- async retrieve(
129
- rootHash: string,
130
- outputPath: string,
131
- options: RetrieveOptions = {}
132
- ): Promise<void> {
133
- return withRetry(
134
- () => this._retrieve(rootHash, outputPath, options),
135
- { maxAttempts: 3 }
136
- )
137
- }
138
-
139
- private async _retrieve(
140
- rootHash: string,
141
- outputPath: string,
142
- options: RetrieveOptions
143
- ): Promise<void> {
144
- const err = await this.indexer.download(
145
- rootHash,
146
- outputPath,
147
- options.proof ?? false
148
- )
149
- if (err) {
150
- const msg = err.message
151
- let suggestion = 'Verify the root hash is correct and the file was uploaded successfully.'
152
- if (msg.includes('not found') || msg.includes('404')) {
153
- suggestion = 'This root hash was not found on the network. Double-check the hash or ensure the upload completed.'
154
- }
155
- throw new StorageError(`Download failed: ${msg}`, suggestion)
156
- }
157
- }
158
- }
package/src/types.ts DELETED
@@ -1,157 +0,0 @@
1
- import type { NetworkName } from './networks.js'
2
-
3
- export interface OrbitConfig {
4
- /** Private key for signing transactions. Falls back to PRIVATE_KEY env var if omitted. */
5
- privateKey?: string
6
- /** Network to connect to */
7
- network: NetworkName
8
- /** Custom RPC URL (overrides network default) */
9
- rpcUrl?: string
10
- /** Custom indexer URL (overrides network default) */
11
- indexerUrl?: string
12
- }
13
-
14
- export interface StoreResult {
15
- /** Merkle root hash — use this to retrieve the file */
16
- root: string
17
- /** On-chain transaction hash */
18
- txHash: string
19
- }
20
-
21
- export interface StoreOptions {
22
- /** Custom tags for the upload (hex string) */
23
- tags?: string
24
- /** Expected number of replicas */
25
- replicas?: number
26
- /** Maximum gas price in wei */
27
- maxGasPrice?: bigint
28
- }
29
-
30
- export interface RetrieveOptions {
31
- /** Whether to verify merkle proofs during download */
32
- proof?: boolean
33
- }
34
-
35
- export interface InferResult {
36
- /** The model's response text */
37
- content: string
38
- /** The model used */
39
- model: string
40
- /** Token usage */
41
- usage?: {
42
- promptTokens: number
43
- completionTokens: number
44
- totalTokens: number
45
- }
46
- /** Whether the response passed TEE verification (null if not verifiable) */
47
- verified: boolean | null
48
- }
49
-
50
- export interface InferOptions {
51
- /** Messages for chat completion */
52
- messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>
53
- /** Temperature for sampling (0-2) */
54
- temperature?: number
55
- /** Maximum tokens to generate */
56
- maxTokens?: number
57
- /** Specific provider address (auto-selected if omitted) */
58
- provider?: string
59
- /** Request timeout in milliseconds (default: 30000) */
60
- timeout?: number
61
- }
62
-
63
- export interface ServiceInfo {
64
- /** Provider wallet address */
65
- provider: string
66
- /** Model name */
67
- model: string
68
- /** Service endpoint URL */
69
- url: string
70
- /** Input token price (neuron) */
71
- inputPrice: bigint
72
- /** Output token price (neuron) */
73
- outputPrice: bigint
74
- /** Whether TEE verification is available */
75
- verifiable: boolean
76
- }
77
-
78
- export interface AccountStatus {
79
- /** Main ledger balance in OG */
80
- balance: string
81
- /** Network name */
82
- network: NetworkName
83
- /** Wallet address */
84
- address: string
85
- }
86
-
87
- // --- Fine-Tuning ---
88
-
89
- export interface DatasetUploadResult {
90
- /** Merkle root hash of the uploaded dataset in 0G Storage */
91
- root: string
92
- /** On-chain transaction hash */
93
- txHash: string
94
- }
95
-
96
- export interface CreateTaskOptions {
97
- /** Name of the base model to fine-tune (e.g. 'Qwen2.5-0.5B-Instruct') */
98
- model: string
99
- /** Root hash of the dataset in 0G Storage (from uploadDataset) */
100
- dataset: string
101
- /** Provider wallet address */
102
- providerAddress: string
103
- /** Training hyperparameters (JSON object or file path) */
104
- trainingParams?: {
105
- nEpochs?: number
106
- batchSize?: number
107
- learningRate?: number
108
- loraRank?: number
109
- loraAlpha?: number
110
- [key: string]: unknown
111
- }
112
- }
113
-
114
- export type FineTuneStatus =
115
- | 'init'
116
- | 'setting-up'
117
- | 'set-up'
118
- | 'training'
119
- | 'trained'
120
- | 'delivering'
121
- | 'delivered'
122
- | 'acknowledged'
123
- | 'finished'
124
- | 'failed'
125
-
126
- export interface FineTuneTask {
127
- /** Task ID */
128
- id: string
129
- /** Base model name or hash */
130
- model: string
131
- /** Dataset root hash */
132
- dataset: string
133
- /** Provider address */
134
- provider: string
135
- /** Current task status */
136
- status: FineTuneStatus
137
- /** Task creation time */
138
- createdAt?: string
139
- /** Last update time */
140
- updatedAt?: string
141
- }
142
-
143
- export interface FineTuneModel {
144
- /** Model name */
145
- name: string
146
- /** Model configuration (hash, description, etc.) */
147
- config: Record<string, string>
148
- }
149
-
150
- export interface FineTuneProvider {
151
- /** Provider wallet address */
152
- address: string
153
- /** Provider endpoint URL */
154
- url: string
155
- /** Customized models offered by this provider */
156
- models: string[]
157
- }
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "Node16",
5
- "moduleResolution": "Node16",
6
- "lib": ["ES2022"],
7
- "strict": true,
8
- "esModuleInterop": true,
9
- "skipLibCheck": true,
10
- "forceConsistentCasingInFileNames": true,
11
- "resolveJsonModule": true,
12
- "declaration": true,
13
- "declarationMap": true,
14
- "sourceMap": true,
15
- "outDir": "./dist",
16
- "rootDir": "./src"
17
- },
18
- "include": ["src/**/*.ts"],
19
- "exclude": ["src/**/*.test.ts"]
20
- }
package/vitest.config.ts DELETED
@@ -1,7 +0,0 @@
1
- import { defineConfig } from 'vitest/config'
2
-
3
- export default defineConfig({
4
- test: {
5
- include: ['src/**/*.test.ts'],
6
- },
7
- })