0g-orbit 0.2.2 → 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.
- package/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/dist/cli/cli.js +1 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +23 -1
- package/dist/storage.js.map +1 -1
- package/package.json +28 -4
- package/examples/ai-chatbot/index.ts +0 -74
- package/examples/model-registry/index.ts +0 -137
- package/examples/quick-start/index.ts +0 -65
- package/packages/cli/package.json +0 -30
- package/packages/cli/src/cli.ts +0 -69
- package/packages/cli/src/commands/account.ts +0 -29
- package/packages/cli/src/commands/inference.ts +0 -103
- package/packages/cli/src/commands/init.ts +0 -71
- package/packages/cli/src/commands/storage.ts +0 -91
- package/packages/cli/src/utils.ts +0 -21
- package/packages/cli/tsconfig.json +0 -8
- package/packages/core/package.json +0 -35
- package/packages/core/src/errors.test.ts +0 -99
- package/packages/core/src/errors.ts +0 -79
- package/packages/core/src/index.ts +0 -37
- package/packages/core/src/inference.ts +0 -256
- package/packages/core/src/networks.test.ts +0 -62
- package/packages/core/src/networks.ts +0 -62
- package/packages/core/src/orbit.test.ts +0 -153
- package/packages/core/src/orbit.ts +0 -159
- package/packages/core/src/retry.test.ts +0 -99
- package/packages/core/src/retry.ts +0 -99
- package/packages/core/src/storage.test.ts +0 -199
- package/packages/core/src/storage.ts +0 -158
- package/packages/core/src/types.ts +0 -85
- package/packages/core/tsconfig.json +0 -8
- package/packages/core/vitest.config.ts +0 -7
- package/src/cli/cli.ts +0 -95
- package/src/cli/commands/account.ts +0 -29
- package/src/cli/commands/fine-tuning.ts +0 -169
- package/src/cli/commands/inference.ts +0 -103
- package/src/cli/commands/init.ts +0 -71
- package/src/cli/commands/storage.ts +0 -91
- package/src/cli/utils.ts +0 -21
- package/src/errors.test.ts +0 -99
- package/src/errors.ts +0 -90
- package/src/fine-tuning.test.ts +0 -299
- package/src/fine-tuning.ts +0 -330
- package/src/index.ts +0 -45
- package/src/inference.ts +0 -256
- package/src/networks.test.ts +0 -62
- package/src/networks.ts +0 -62
- package/src/orbit.test.ts +0 -153
- package/src/orbit.ts +0 -204
- package/src/retry.test.ts +0 -99
- package/src/retry.ts +0 -99
- package/src/storage.test.ts +0 -199
- package/src/storage.ts +0 -158
- package/src/types.ts +0 -157
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -7
package/src/networks.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
export type NetworkName = 'testnet' | 'mainnet'
|
|
2
|
-
|
|
3
|
-
export interface NetworkConfig {
|
|
4
|
-
name: NetworkName
|
|
5
|
-
chainId: bigint
|
|
6
|
-
rpcUrl: string
|
|
7
|
-
/** Fallback RPC URLs, tried in order if the primary fails */
|
|
8
|
-
rpcUrls: string[]
|
|
9
|
-
indexerUrl: string
|
|
10
|
-
flowContractAddress: string
|
|
11
|
-
ledgerContractAddress: string
|
|
12
|
-
inferenceContractAddress: string
|
|
13
|
-
fineTuningContractAddress: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const NETWORKS: Record<NetworkName, NetworkConfig> = {
|
|
17
|
-
testnet: {
|
|
18
|
-
name: 'testnet',
|
|
19
|
-
chainId: 16602n,
|
|
20
|
-
rpcUrl: 'https://evmrpc-testnet.0g.ai',
|
|
21
|
-
rpcUrls: [
|
|
22
|
-
'https://evmrpc-testnet.0g.ai',
|
|
23
|
-
'https://16600-rpc.testnet.0g.ai',
|
|
24
|
-
'https://og-testnet-evm-rpc.allthatnode.com',
|
|
25
|
-
],
|
|
26
|
-
indexerUrl: 'https://indexer-storage-testnet-turbo.0g.ai',
|
|
27
|
-
flowContractAddress: '0xbD2C3F0E65eDF5582141C35969d66e34e4E6BF80',
|
|
28
|
-
ledgerContractAddress: '0xE70830508dAc0A97e6c087c75f402f9Be669E406',
|
|
29
|
-
inferenceContractAddress: '0xa79F4c8311FF93C06b8CfB403690cc987c93F91E',
|
|
30
|
-
fineTuningContractAddress: '0xC6C075D8039763C8f1EbE580be5ADdf2fd6941bA',
|
|
31
|
-
},
|
|
32
|
-
mainnet: {
|
|
33
|
-
name: 'mainnet',
|
|
34
|
-
chainId: 16661n,
|
|
35
|
-
rpcUrl: 'https://evmrpc.0g.ai',
|
|
36
|
-
rpcUrls: [
|
|
37
|
-
'https://evmrpc.0g.ai',
|
|
38
|
-
'https://0g-rpc-evm01.validatorvn.com',
|
|
39
|
-
'https://rpc.0g.thirdweb.com',
|
|
40
|
-
],
|
|
41
|
-
indexerUrl: 'https://indexer-storage.0g.ai',
|
|
42
|
-
flowContractAddress: '0x0460aA47b41a66694c0a73f667a1b795A5ED3556',
|
|
43
|
-
ledgerContractAddress: '0x2dE54c845Cd948B72D2e32e39586fe89607074E3',
|
|
44
|
-
inferenceContractAddress: '0x47340d900bdFec2BD393c626E12ea0656F938d84',
|
|
45
|
-
fineTuningContractAddress: '0x4e3474095518883744ddf135b7E0A23301c7F9c0',
|
|
46
|
-
},
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function getNetwork(nameOrChainId: NetworkName | bigint): NetworkConfig {
|
|
50
|
-
if (typeof nameOrChainId === 'string') {
|
|
51
|
-
const config = NETWORKS[nameOrChainId]
|
|
52
|
-
if (!config) throw new Error(`Unknown network: ${nameOrChainId}`)
|
|
53
|
-
// Return a copy so overrides don't mutate the original
|
|
54
|
-
return { ...config, rpcUrls: [...config.rpcUrls] }
|
|
55
|
-
}
|
|
56
|
-
for (const config of Object.values(NETWORKS)) {
|
|
57
|
-
if (config.chainId === nameOrChainId) {
|
|
58
|
-
return { ...config, rpcUrls: [...config.rpcUrls] }
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
throw new Error(`Unknown chain ID: ${nameOrChainId}`)
|
|
62
|
-
}
|
package/src/orbit.test.ts
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
-
import { ConnectionError } from './errors.js'
|
|
3
|
-
|
|
4
|
-
// Mock ethers before importing Orbit
|
|
5
|
-
vi.mock('ethers', () => {
|
|
6
|
-
const mockProvider = {
|
|
7
|
-
getNetwork: vi.fn().mockResolvedValue({ chainId: 16602n }),
|
|
8
|
-
getBalance: vi.fn().mockResolvedValue(1000000000000000000n), // 1 OG
|
|
9
|
-
}
|
|
10
|
-
const MockJsonRpcProvider = vi.fn(() => mockProvider)
|
|
11
|
-
const MockWallet = vi.fn((key: string, provider: any) => ({
|
|
12
|
-
address: '0x1234567890abcdef1234567890abcdef12345678',
|
|
13
|
-
provider,
|
|
14
|
-
}))
|
|
15
|
-
return {
|
|
16
|
-
JsonRpcProvider: MockJsonRpcProvider,
|
|
17
|
-
Wallet: MockWallet,
|
|
18
|
-
}
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
// Mock the storage and inference clients
|
|
22
|
-
vi.mock('./storage.js', () => ({
|
|
23
|
-
StorageClient: vi.fn(),
|
|
24
|
-
}))
|
|
25
|
-
vi.mock('./inference.js', () => ({
|
|
26
|
-
InferenceClient: vi.fn(),
|
|
27
|
-
}))
|
|
28
|
-
|
|
29
|
-
import { Orbit } from './orbit.js'
|
|
30
|
-
import { JsonRpcProvider } from 'ethers'
|
|
31
|
-
|
|
32
|
-
describe('Orbit.connect', () => {
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
vi.clearAllMocks()
|
|
35
|
-
delete process.env.PRIVATE_KEY
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('connects with explicit private key', async () => {
|
|
39
|
-
const orbit = await Orbit.connect({
|
|
40
|
-
network: 'testnet',
|
|
41
|
-
privateKey: '0xabc123',
|
|
42
|
-
})
|
|
43
|
-
expect(orbit.address).toBe('0x1234567890abcdef1234567890abcdef12345678')
|
|
44
|
-
expect(orbit.network.name).toBe('testnet')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('reads PRIVATE_KEY from env var', async () => {
|
|
48
|
-
process.env.PRIVATE_KEY = '0xenv_key_123'
|
|
49
|
-
const orbit = await Orbit.connect({ network: 'testnet' })
|
|
50
|
-
expect(orbit.address).toBeDefined()
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('throws with suggestion when no private key available', async () => {
|
|
54
|
-
await expect(
|
|
55
|
-
Orbit.connect({ network: 'testnet' })
|
|
56
|
-
).rejects.toThrow('No private key provided')
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
await Orbit.connect({ network: 'testnet' })
|
|
60
|
-
} catch (err) {
|
|
61
|
-
expect(err).toBeInstanceOf(ConnectionError)
|
|
62
|
-
expect((err as ConnectionError).suggestion).toContain('PRIVATE_KEY')
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('throws on chain ID mismatch', async () => {
|
|
67
|
-
const { JsonRpcProvider } = await import('ethers')
|
|
68
|
-
const mockProvider = new JsonRpcProvider() as any
|
|
69
|
-
mockProvider.getNetwork.mockResolvedValueOnce({ chainId: 99999n })
|
|
70
|
-
|
|
71
|
-
await expect(
|
|
72
|
-
Orbit.connect({ network: 'testnet', privateKey: '0xabc' })
|
|
73
|
-
).rejects.toThrow('Chain ID mismatch')
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('warns on zero balance but still connects', async () => {
|
|
77
|
-
const { JsonRpcProvider } = await import('ethers')
|
|
78
|
-
const mockProvider = new JsonRpcProvider() as any
|
|
79
|
-
mockProvider.getNetwork.mockResolvedValueOnce({ chainId: 16602n })
|
|
80
|
-
mockProvider.getBalance.mockResolvedValueOnce(0n)
|
|
81
|
-
|
|
82
|
-
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
83
|
-
|
|
84
|
-
const orbit = await Orbit.connect({
|
|
85
|
-
network: 'testnet',
|
|
86
|
-
privateKey: '0xabc',
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
expect(orbit).toBeDefined()
|
|
90
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
91
|
-
expect.stringContaining('0 OG balance')
|
|
92
|
-
)
|
|
93
|
-
warnSpy.mockRestore()
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it('tries fallback RPC URLs on connection failure', async () => {
|
|
97
|
-
const { JsonRpcProvider } = await import('ethers')
|
|
98
|
-
|
|
99
|
-
// First two URLs fail, third succeeds
|
|
100
|
-
let callCount = 0
|
|
101
|
-
;(JsonRpcProvider as any).mockImplementation(() => {
|
|
102
|
-
callCount++
|
|
103
|
-
const provider = {
|
|
104
|
-
getNetwork: callCount < 3
|
|
105
|
-
? vi.fn().mockRejectedValue(new Error('ECONNREFUSED'))
|
|
106
|
-
: vi.fn().mockResolvedValue({ chainId: 16602n }),
|
|
107
|
-
getBalance: vi.fn().mockResolvedValue(1000000000000000000n),
|
|
108
|
-
}
|
|
109
|
-
return provider
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
const orbit = await Orbit.connect({
|
|
113
|
-
network: 'testnet',
|
|
114
|
-
privateKey: '0xabc',
|
|
115
|
-
})
|
|
116
|
-
expect(orbit).toBeDefined()
|
|
117
|
-
expect(callCount).toBe(3)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('throws when all RPC URLs fail', async () => {
|
|
121
|
-
const { JsonRpcProvider } = await import('ethers')
|
|
122
|
-
;(JsonRpcProvider as any).mockImplementation(() => ({
|
|
123
|
-
getNetwork: vi.fn().mockRejectedValue(new Error('ECONNREFUSED')),
|
|
124
|
-
}))
|
|
125
|
-
|
|
126
|
-
await expect(
|
|
127
|
-
Orbit.connect({ network: 'testnet', privateKey: '0xabc' })
|
|
128
|
-
).rejects.toThrow('Failed to connect to any RPC endpoint')
|
|
129
|
-
})
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
describe('Orbit.status', () => {
|
|
133
|
-
beforeEach(() => {
|
|
134
|
-
vi.clearAllMocks()
|
|
135
|
-
// Restore default mock after tests that override JsonRpcProvider
|
|
136
|
-
const mockProvider = {
|
|
137
|
-
getNetwork: vi.fn().mockResolvedValue({ chainId: 16602n }),
|
|
138
|
-
getBalance: vi.fn().mockResolvedValue(1000000000000000000n),
|
|
139
|
-
}
|
|
140
|
-
;(JsonRpcProvider as any).mockImplementation(() => mockProvider)
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
it('returns formatted balance and address', async () => {
|
|
144
|
-
const orbit = await Orbit.connect({
|
|
145
|
-
network: 'testnet',
|
|
146
|
-
privateKey: '0xabc',
|
|
147
|
-
})
|
|
148
|
-
const status = await orbit.status()
|
|
149
|
-
expect(status.network).toBe('testnet')
|
|
150
|
-
expect(status.address).toBe('0x1234567890abcdef1234567890abcdef12345678')
|
|
151
|
-
expect(status.balance).toMatch(/^\d+\.\d{6}$/)
|
|
152
|
-
})
|
|
153
|
-
})
|
package/src/orbit.ts
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { Wallet, JsonRpcProvider } from 'ethers'
|
|
2
|
-
import type { NetworkName, NetworkConfig } from './networks.js'
|
|
3
|
-
import { getNetwork, NETWORKS } from './networks.js'
|
|
4
|
-
import { StorageClient } from './storage.js'
|
|
5
|
-
import { InferenceClient } from './inference.js'
|
|
6
|
-
import { FineTuningClient } from './fine-tuning.js'
|
|
7
|
-
import type {
|
|
8
|
-
OrbitConfig,
|
|
9
|
-
StoreResult,
|
|
10
|
-
StoreOptions,
|
|
11
|
-
RetrieveOptions,
|
|
12
|
-
InferResult,
|
|
13
|
-
InferOptions,
|
|
14
|
-
ServiceInfo,
|
|
15
|
-
AccountStatus,
|
|
16
|
-
DatasetUploadResult,
|
|
17
|
-
CreateTaskOptions,
|
|
18
|
-
FineTuneTask,
|
|
19
|
-
FineTuneModel,
|
|
20
|
-
FineTuneProvider,
|
|
21
|
-
} from './types.js'
|
|
22
|
-
import { ConnectionError } from './errors.js'
|
|
23
|
-
|
|
24
|
-
export class Orbit {
|
|
25
|
-
readonly network: NetworkConfig
|
|
26
|
-
readonly storage: StorageClient
|
|
27
|
-
readonly inference: InferenceClient
|
|
28
|
-
private _fineTuning: FineTuningClient | null = null
|
|
29
|
-
private wallet: Wallet
|
|
30
|
-
private provider: JsonRpcProvider
|
|
31
|
-
|
|
32
|
-
private constructor(
|
|
33
|
-
network: NetworkConfig,
|
|
34
|
-
wallet: Wallet,
|
|
35
|
-
provider: JsonRpcProvider
|
|
36
|
-
) {
|
|
37
|
-
this.network = network
|
|
38
|
-
this.wallet = wallet
|
|
39
|
-
this.provider = provider
|
|
40
|
-
this.storage = new StorageClient(network, wallet)
|
|
41
|
-
this.inference = new InferenceClient(network, wallet)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Lazy-initialized fine-tuning client */
|
|
45
|
-
get fineTuning(): FineTuningClient {
|
|
46
|
-
if (!this._fineTuning) {
|
|
47
|
-
this._fineTuning = new FineTuningClient(this.network, this.wallet, this.storage)
|
|
48
|
-
}
|
|
49
|
-
return this._fineTuning
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Connect to the 0G network. This is the primary entry point.
|
|
54
|
-
*
|
|
55
|
-
* Tries the primary RPC URL first, then falls back to alternates.
|
|
56
|
-
* If privateKey is not provided, reads from PRIVATE_KEY env var.
|
|
57
|
-
*/
|
|
58
|
-
static async connect(config: OrbitConfig): Promise<Orbit> {
|
|
59
|
-
// Resolve private key: explicit > env var
|
|
60
|
-
const privateKey = config.privateKey ?? process.env.PRIVATE_KEY
|
|
61
|
-
if (!privateKey) {
|
|
62
|
-
throw new ConnectionError(
|
|
63
|
-
'No private key provided.',
|
|
64
|
-
'Pass privateKey in config or set the PRIVATE_KEY environment variable. Example: PRIVATE_KEY=0x... npx tsx index.ts'
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const networkConfig = getNetwork(config.network)
|
|
69
|
-
|
|
70
|
-
// Allow overrides
|
|
71
|
-
if (config.rpcUrl) networkConfig.rpcUrl = config.rpcUrl
|
|
72
|
-
if (config.indexerUrl) networkConfig.indexerUrl = config.indexerUrl
|
|
73
|
-
|
|
74
|
-
// Build candidate RPC URLs: explicit override first, then the fallback list
|
|
75
|
-
const rpcUrls = config.rpcUrl
|
|
76
|
-
? [config.rpcUrl]
|
|
77
|
-
: networkConfig.rpcUrls
|
|
78
|
-
|
|
79
|
-
// Try each RPC URL until one connects
|
|
80
|
-
let lastError: Error | null = null
|
|
81
|
-
for (const url of rpcUrls) {
|
|
82
|
-
try {
|
|
83
|
-
const provider = new JsonRpcProvider(url)
|
|
84
|
-
const wallet = new Wallet(privateKey, provider)
|
|
85
|
-
|
|
86
|
-
const chainId = (await provider.getNetwork()).chainId
|
|
87
|
-
if (chainId !== networkConfig.chainId) {
|
|
88
|
-
throw new ConnectionError(
|
|
89
|
-
`Chain ID mismatch: expected ${networkConfig.chainId}, got ${chainId}`,
|
|
90
|
-
`Your RPC endpoint is on a different network. Use the correct RPC URL for ${networkConfig.name}.`
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Lock in the working URL
|
|
95
|
-
networkConfig.rpcUrl = url
|
|
96
|
-
|
|
97
|
-
// Check balance and warn if zero (non-blocking)
|
|
98
|
-
try {
|
|
99
|
-
const balance = await provider.getBalance(wallet.address)
|
|
100
|
-
if (balance === 0n) {
|
|
101
|
-
console.warn(
|
|
102
|
-
`[0G Orbit] Warning: wallet ${wallet.address} has 0 OG balance on ${networkConfig.name}. ` +
|
|
103
|
-
`Get testnet tokens at https://faucet.0g.ai`
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
} catch {
|
|
107
|
-
// Balance check failure is non-fatal
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return new Orbit(networkConfig, wallet, provider)
|
|
111
|
-
} catch (err) {
|
|
112
|
-
// Chain ID mismatch is not a transient failure — don't try other URLs
|
|
113
|
-
if (err instanceof ConnectionError && err.message.includes('Chain ID mismatch')) {
|
|
114
|
-
throw err
|
|
115
|
-
}
|
|
116
|
-
lastError = err instanceof Error ? err : new Error(String(err))
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const tried = rpcUrls.join(', ')
|
|
121
|
-
throw new ConnectionError(
|
|
122
|
-
`Failed to connect to any RPC endpoint. Tried: ${tried}. Last error: ${lastError?.message ?? 'unknown'}`,
|
|
123
|
-
'All RPC endpoints are unreachable. Check your internet connection, or provide a custom RPC URL with the rpcUrl option.'
|
|
124
|
-
)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// --- Storage shortcuts ---
|
|
128
|
-
|
|
129
|
-
async store(filePath: string, options?: StoreOptions): Promise<StoreResult> {
|
|
130
|
-
return this.storage.store(filePath, options)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async storeData(
|
|
134
|
-
data: string | Buffer | Uint8Array,
|
|
135
|
-
options?: StoreOptions
|
|
136
|
-
): Promise<StoreResult> {
|
|
137
|
-
return this.storage.storeData(data, options)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async retrieve(
|
|
141
|
-
rootHash: string,
|
|
142
|
-
outputPath: string,
|
|
143
|
-
options?: RetrieveOptions
|
|
144
|
-
): Promise<void> {
|
|
145
|
-
return this.storage.retrieve(rootHash, outputPath, options)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// --- Inference shortcuts ---
|
|
149
|
-
|
|
150
|
-
async infer(model: string, options: InferOptions): Promise<InferResult> {
|
|
151
|
-
return this.inference.infer(model, options)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async listServices(): Promise<ServiceInfo[]> {
|
|
155
|
-
return this.inference.listServices()
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// --- Fine-Tuning shortcuts ---
|
|
159
|
-
|
|
160
|
-
async uploadDataset(filePath: string): Promise<DatasetUploadResult> {
|
|
161
|
-
return this.fineTuning.uploadDataset(filePath)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async createFineTuneTask(options: CreateTaskOptions): Promise<FineTuneTask> {
|
|
165
|
-
return this.fineTuning.createTask(options)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async getFineTuneTask(providerAddress: string, taskId: string): Promise<FineTuneTask> {
|
|
169
|
-
return this.fineTuning.getTask(providerAddress, taskId)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async downloadModel(
|
|
173
|
-
providerAddress: string,
|
|
174
|
-
taskId: string,
|
|
175
|
-
outputPath: string
|
|
176
|
-
): Promise<void> {
|
|
177
|
-
return this.fineTuning.downloadModel(providerAddress, taskId, outputPath)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async listModels(): Promise<FineTuneModel[]> {
|
|
181
|
-
return this.fineTuning.listModels()
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async listProviders(): Promise<FineTuneProvider[]> {
|
|
185
|
-
return this.fineTuning.listProviders()
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// --- Account ---
|
|
189
|
-
|
|
190
|
-
async status(): Promise<AccountStatus> {
|
|
191
|
-
const balance = await this.provider.getBalance(this.wallet.address)
|
|
192
|
-
const balanceOG = Number(balance) / 1e18
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
balance: balanceOG.toFixed(6),
|
|
196
|
-
network: this.network.name,
|
|
197
|
-
address: this.wallet.address,
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
get address(): string {
|
|
202
|
-
return this.wallet.address
|
|
203
|
-
}
|
|
204
|
-
}
|
package/src/retry.test.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
-
import { withRetry, isTransientError } from './retry.js'
|
|
3
|
-
|
|
4
|
-
describe('withRetry', () => {
|
|
5
|
-
it('returns on first success', async () => {
|
|
6
|
-
const fn = vi.fn().mockResolvedValue('ok')
|
|
7
|
-
const result = await withRetry(fn)
|
|
8
|
-
expect(result).toBe('ok')
|
|
9
|
-
expect(fn).toHaveBeenCalledTimes(1)
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('retries on transient error and succeeds', async () => {
|
|
13
|
-
const fn = vi.fn()
|
|
14
|
-
.mockRejectedValueOnce(new Error('ECONNREFUSED'))
|
|
15
|
-
.mockResolvedValue('ok')
|
|
16
|
-
|
|
17
|
-
const result = await withRetry(fn, { baseDelay: 10 })
|
|
18
|
-
expect(result).toBe('ok')
|
|
19
|
-
expect(fn).toHaveBeenCalledTimes(2)
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('throws after maxAttempts exhausted', async () => {
|
|
23
|
-
const fn = vi.fn().mockRejectedValue(new Error('ETIMEDOUT'))
|
|
24
|
-
|
|
25
|
-
await expect(
|
|
26
|
-
withRetry(fn, { maxAttempts: 3, baseDelay: 10 })
|
|
27
|
-
).rejects.toThrow('ETIMEDOUT')
|
|
28
|
-
expect(fn).toHaveBeenCalledTimes(3)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('does not retry non-transient errors', async () => {
|
|
32
|
-
const fn = vi.fn().mockRejectedValue(new Error('Invalid private key'))
|
|
33
|
-
|
|
34
|
-
await expect(
|
|
35
|
-
withRetry(fn, { maxAttempts: 3, baseDelay: 10 })
|
|
36
|
-
).rejects.toThrow('Invalid private key')
|
|
37
|
-
expect(fn).toHaveBeenCalledTimes(1)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('calls onRetry callback', async () => {
|
|
41
|
-
const onRetry = vi.fn()
|
|
42
|
-
const fn = vi.fn()
|
|
43
|
-
.mockRejectedValueOnce(new Error('ECONNRESET'))
|
|
44
|
-
.mockResolvedValue('ok')
|
|
45
|
-
|
|
46
|
-
await withRetry(fn, { baseDelay: 10, onRetry })
|
|
47
|
-
expect(onRetry).toHaveBeenCalledTimes(1)
|
|
48
|
-
expect(onRetry).toHaveBeenCalledWith(1, expect.any(Error))
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('respects custom isRetryable predicate', async () => {
|
|
52
|
-
const fn = vi.fn()
|
|
53
|
-
.mockRejectedValueOnce(new Error('custom-retryable'))
|
|
54
|
-
.mockResolvedValue('ok')
|
|
55
|
-
|
|
56
|
-
const result = await withRetry(fn, {
|
|
57
|
-
baseDelay: 10,
|
|
58
|
-
isRetryable: (err) => err instanceof Error && err.message === 'custom-retryable',
|
|
59
|
-
})
|
|
60
|
-
expect(result).toBe('ok')
|
|
61
|
-
expect(fn).toHaveBeenCalledTimes(2)
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
describe('isTransientError', () => {
|
|
66
|
-
it.each([
|
|
67
|
-
'ECONNREFUSED',
|
|
68
|
-
'ECONNRESET',
|
|
69
|
-
'ETIMEDOUT',
|
|
70
|
-
'ENOTFOUND',
|
|
71
|
-
'fetch failed',
|
|
72
|
-
'network error',
|
|
73
|
-
'timeout exceeded',
|
|
74
|
-
'server error',
|
|
75
|
-
'bad gateway',
|
|
76
|
-
'service unavailable',
|
|
77
|
-
'rate limit exceeded',
|
|
78
|
-
'missing response',
|
|
79
|
-
'connection error',
|
|
80
|
-
'could not detect network',
|
|
81
|
-
])('returns true for "%s"', (msg) => {
|
|
82
|
-
expect(isTransientError(new Error(msg))).toBe(true)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it.each([
|
|
86
|
-
'Invalid private key',
|
|
87
|
-
'insufficient funds for gas',
|
|
88
|
-
'File not found',
|
|
89
|
-
'unknown error',
|
|
90
|
-
])('returns false for "%s"', (msg) => {
|
|
91
|
-
expect(isTransientError(new Error(msg))).toBe(false)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('returns false for non-Error values', () => {
|
|
95
|
-
expect(isTransientError('string error')).toBe(false)
|
|
96
|
-
expect(isTransientError(null)).toBe(false)
|
|
97
|
-
expect(isTransientError(42)).toBe(false)
|
|
98
|
-
})
|
|
99
|
-
})
|
package/src/retry.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
export interface RetryOptions {
|
|
2
|
-
/** Maximum number of attempts (default: 3) */
|
|
3
|
-
maxAttempts?: number
|
|
4
|
-
/** Base delay between retries in ms (default: 1000). Doubles each retry. */
|
|
5
|
-
baseDelay?: number
|
|
6
|
-
/** Maximum delay cap in ms (default: 10000) */
|
|
7
|
-
maxDelay?: number
|
|
8
|
-
/** Predicate to decide if an error is retryable (default: transient errors only) */
|
|
9
|
-
isRetryable?: (err: unknown) => boolean
|
|
10
|
-
/** Called before each retry with attempt number and error */
|
|
11
|
-
onRetry?: (attempt: number, err: unknown) => void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const DEFAULT_OPTIONS: Required<Omit<RetryOptions, 'onRetry'>> = {
|
|
15
|
-
maxAttempts: 3,
|
|
16
|
-
baseDelay: 1000,
|
|
17
|
-
maxDelay: 10_000,
|
|
18
|
-
isRetryable: isTransientError,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Retry an async operation with exponential backoff.
|
|
23
|
-
* Only retries on transient errors (network, timeout, 5xx) by default.
|
|
24
|
-
*/
|
|
25
|
-
export async function withRetry<T>(
|
|
26
|
-
fn: () => Promise<T>,
|
|
27
|
-
options: RetryOptions = {}
|
|
28
|
-
): Promise<T> {
|
|
29
|
-
const opts = { ...DEFAULT_OPTIONS, ...options }
|
|
30
|
-
let lastError: unknown
|
|
31
|
-
|
|
32
|
-
for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
|
|
33
|
-
try {
|
|
34
|
-
return await fn()
|
|
35
|
-
} catch (err) {
|
|
36
|
-
lastError = err
|
|
37
|
-
|
|
38
|
-
if (attempt === opts.maxAttempts || !opts.isRetryable(err)) {
|
|
39
|
-
throw err
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (options.onRetry) {
|
|
43
|
-
options.onRetry(attempt, err)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Exponential backoff with jitter
|
|
47
|
-
const delay = Math.min(
|
|
48
|
-
opts.baseDelay * Math.pow(2, attempt - 1) + Math.random() * 500,
|
|
49
|
-
opts.maxDelay
|
|
50
|
-
)
|
|
51
|
-
await sleep(delay)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
throw lastError
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function sleep(ms: number): Promise<void> {
|
|
59
|
-
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Determines if an error is transient and worth retrying.
|
|
64
|
-
* Covers: network failures, timeouts, RPC errors, 5xx responses.
|
|
65
|
-
*/
|
|
66
|
-
export function isTransientError(err: unknown): boolean {
|
|
67
|
-
if (!(err instanceof Error)) return false
|
|
68
|
-
const msg = err.message.toLowerCase()
|
|
69
|
-
|
|
70
|
-
// Network-level failures
|
|
71
|
-
if (msg.includes('econnrefused')) return true
|
|
72
|
-
if (msg.includes('econnreset')) return true
|
|
73
|
-
if (msg.includes('etimedout')) return true
|
|
74
|
-
if (msg.includes('enotfound')) return true
|
|
75
|
-
if (msg.includes('epipe')) return true
|
|
76
|
-
if (msg.includes('fetch failed')) return true
|
|
77
|
-
if (msg.includes('network error')) return true
|
|
78
|
-
|
|
79
|
-
// Timeout
|
|
80
|
-
if (msg.includes('timeout')) return true
|
|
81
|
-
if (err.name === 'AbortError') return true
|
|
82
|
-
|
|
83
|
-
// RPC-level transient errors
|
|
84
|
-
if (msg.includes('server error')) return true
|
|
85
|
-
if (msg.includes('bad gateway')) return true
|
|
86
|
-
if (msg.includes('service unavailable')) return true
|
|
87
|
-
if (msg.includes('rate limit')) return true
|
|
88
|
-
if (msg.includes('429')) return true
|
|
89
|
-
if (msg.includes('502')) return true
|
|
90
|
-
if (msg.includes('503')) return true
|
|
91
|
-
if (msg.includes('504')) return true
|
|
92
|
-
|
|
93
|
-
// ethers provider errors
|
|
94
|
-
if (msg.includes('missing response')) return true
|
|
95
|
-
if (msg.includes('connection error')) return true
|
|
96
|
-
if (msg.includes('could not detect network')) return true
|
|
97
|
-
|
|
98
|
-
return false
|
|
99
|
-
}
|