Skip to content

Commit 87691a5

Browse files
authored
Add ZGComputeNetworkReadOnlyBroker (#164)
* Add ZGComputeNetworkReadOnlyBroker * reconstruct * less change * update ci
1 parent 00e1325 commit 87691a5

File tree

12 files changed

+695
-348
lines changed

12 files changed

+695
-348
lines changed

.github/workflows/cli-test.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,32 @@ jobs:
8686
# Test CLI commands using local binary
8787
if [ -f "./node_modules/.bin/0g-compute-cli" ]; then
8888
# npm/yarn classic with node_modules/.bin
89+
echo "Testing basic CLI commands..."
8990
./node_modules/.bin/0g-compute-cli --version
9091
./node_modules/.bin/0g-compute-cli --help
9192
./node_modules/.bin/0g-compute-cli inference --help
9293
./node_modules/.bin/0g-compute-cli show-network || echo "Network command tested"
94+
95+
echo "Testing authentication-free commands..."
96+
echo "→ Testing inference list-providers (no auth required)"
97+
./node_modules/.bin/0g-compute-cli inference list-providers --rpc https://evmrpc-testnet.0g.ai
98+
99+
echo "→ Testing inference list-providers-detail (no auth required)"
100+
./node_modules/.bin/0g-compute-cli inference list-providers-detail --rpc https://evmrpc-testnet.0g.ai
93101
elif [ "${{ matrix.pkg-manager }}" = "yarn" ]; then
94102
# yarn 4+ with PnP mode
103+
echo "Testing basic CLI commands..."
95104
yarn run 0g-compute-cli --version
96105
yarn run 0g-compute-cli --help
97106
yarn run 0g-compute-cli inference --help
98107
yarn run 0g-compute-cli show-network || echo "Network command tested"
108+
109+
echo "Testing authentication-free commands..."
110+
echo "→ Testing inference list-providers (no auth required)"
111+
yarn run 0g-compute-cli inference list-providers --rpc https://evmrpc-testnet.0g.ai
112+
113+
echo "→ Testing inference list-providers-detail (no auth required)"
114+
yarn run 0g-compute-cli inference list-providers-detail --rpc https://evmrpc-testnet.0g.ai
99115
else
100116
echo "CLI binary not found"
101117
exit 1

src.ts/cli/inference.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env ts-node
22

33
import type { Command } from 'commander'
4-
import { withBroker, neuronToA0gi, a0giToNeuron, initBroker } from './util'
4+
import { withBroker, withROBroker, neuronToA0gi, a0giToNeuron, initBroker } from './util'
55
import { getRpcEndpoint } from './network-setup'
66
import { ensurePrivateKeyConfiguration } from './private-key-setup'
77
import { interactiveSelect, textInput } from './interactive-selection'
@@ -78,12 +78,11 @@ export default function inference(program: Command) {
7878
'--include-invalid',
7979
'Include all services, even those without valid teeSignerAddress'
8080
)
81-
.action((options: any) => {
81+
.action(async (options: any) => {
8282
const table = new Table({
8383
colWidths: [50, 50],
8484
})
85-
withBroker(options, async (broker) => {
86-
// TODO: Support pagination for listing services
85+
await withROBroker(options, async (broker) => {
8786
const services = await broker.inference.listService(
8887
0,
8988
50,
@@ -138,7 +137,7 @@ export default function inference(program: Command) {
138137
program
139138
.command('list-providers-detail')
140139
.description(
141-
'List inference providers with health metrics (uptime and latency)'
140+
'List inference providers with health metrics'
142141
)
143142
.option('--rpc <url>', '0G Chain RPC endpoint')
144143
.option('--ledger-ca <address>', 'Account (ledger) contract address')
@@ -147,12 +146,11 @@ export default function inference(program: Command) {
147146
'--include-invalid',
148147
'Include all services, even those without valid teeSignerAddress'
149148
)
150-
.action((options: any) => {
149+
.action(async (options: any) => {
151150
const table = new Table({
152151
colWidths: [50, 50],
153152
})
154-
withBroker(options, async (broker) => {
155-
// TODO: Support pagination for listing services
153+
await withROBroker(options, async (broker) => {
156154
const services = await broker.inference.listServiceWithDetail(
157155
0,
158156
50,

src.ts/cli/network-setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export async function ensureNetworkConfiguration(): Promise<string> {
153153
/**
154154
* Gets the RPC endpoint, with interactive setup if needed
155155
*/
156-
export async function getRpcEndpoint(options: any): Promise<string> {
156+
export async function getRpcEndpoint(options: { rpc?: string }): Promise<string> {
157157
// Priority: CLI option > environment variable > interactive setup
158158
if (options.rpc) {
159159
return options.rpc

src.ts/cli/util.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { ZGComputeNetworkBroker } from '../sdk'
2-
import { createZGComputeNetworkBroker } from '../sdk'
1+
import type { ZGComputeNetworkBroker, ZGComputeNetworkReadOnlyBroker } from '../sdk'
2+
import { createZGComputeNetworkBroker, createZGComputeNetworkReadOnlyBroker } from '../sdk'
33
import { ethers } from 'ethers'
44
import chalk from 'chalk'
55
import type { Table } from 'cli-table3'
@@ -46,6 +46,25 @@ export async function withBroker(
4646
}
4747
}
4848

49+
/**
50+
* Utility function for commands that only need read-only access (no authentication)
51+
* Use this for listing providers, getting service info, etc.
52+
*/
53+
export async function withROBroker(
54+
options: any,
55+
action: (broker: ZGComputeNetworkReadOnlyBroker) => Promise<void>
56+
) {
57+
try {
58+
const rpcEndpoint = await getRpcEndpoint(options)
59+
const broker = await createZGComputeNetworkReadOnlyBroker(rpcEndpoint)
60+
await action(broker)
61+
process.exit(0)
62+
} catch (error: any) {
63+
alertError(error)
64+
process.exit(1)
65+
}
66+
}
67+
4968
export async function withFineTuningBroker(
5069
options: any,
5170
action: (broker: ZGComputeNetworkBroker) => Promise<void>

src.ts/sdk/broker.ts

Lines changed: 117 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,28 @@
11
import type { JsonRpcSigner } from 'ethers'
2-
import { Wallet } from 'ethers'
2+
import { Wallet, JsonRpcProvider } from 'ethers'
33
import { createLedgerBroker } from './ledger'
44
import { createFineTuningBroker } from './fine-tuning/broker'
55
import { createInferenceBroker } from './inference/broker/broker'
66
import type { InferenceBroker } from './inference/broker/broker'
77
import type { LedgerBroker } from './ledger'
88
import type { FineTuningBroker } from './fine-tuning/broker'
9+
import { createReadOnlyInferenceBroker } from './inference/broker/read-only-broker'
10+
import type { ReadOnlyInferenceBroker } from './inference/broker/read-only-broker'
11+
import {
12+
TESTNET_CHAIN_ID,
13+
MAINNET_CHAIN_ID,
14+
HARDHAT_CHAIN_ID,
15+
CONTRACT_ADDRESSES,
16+
isDevMode,
17+
} from './constants'
918

10-
// Network configurations
11-
export const TESTNET_CHAIN_ID = 16602n
12-
export const MAINNET_CHAIN_ID = 16661n
13-
export const HARDHAT_CHAIN_ID = 31337n
14-
15-
// Contract addresses for different networks
16-
export const CONTRACT_ADDRESSES = {
17-
testnet: {
18-
ledger: '0xE70830508dAc0A97e6c087c75f402f9Be669E406',
19-
inference: '0xa79F4c8311FF93C06b8CfB403690cc987c93F91E',
20-
fineTuning: '0xC6C075D8039763C8f1EbE580be5ADdf2fd6941bA',
21-
},
22-
testnetDev: {
23-
ledger: '0x815B93ab4Ba4BDF530dbF1552649a3c534F8BbF7',
24-
inference: '0x41bD7Ac5c19000A974D5c192bcd5FB67b56C85c5',
25-
fineTuning: '0x4e4158DF35CfdC0ac63264D3E112F5B8E9a5c569',
26-
},
27-
mainnet: {
28-
ledger: '0x2dE54c845Cd948B72D2e32e39586fe89607074E3',
29-
inference: '0x47340d900bdFec2BD393c626E12ea0656F938d84',
30-
fineTuning: '0x4e3474095518883744ddf135b7E0A23301c7F9c0',
31-
},
32-
hardhat: {
33-
ledger: '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0',
34-
inference: '0x0165878A594ca255338adfa4d48449f69242Eb8F',
35-
fineTuning: '0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0',
36-
},
37-
} as const
38-
39-
/**
40-
* Check if dev mode is enabled
41-
* Supports multiple ways to enable dev mode:
42-
* - Node.js: ZG_DEV_MODE environment variable
43-
* - Next.js: NEXT_PUBLIC_ZG_DEV_MODE environment variable (build-time)
44-
* - Browser: localStorage 'ZG_DEV_MODE' = 'true'
45-
* - Browser: URL parameter ?dev=true or ?ZG_DEV_MODE=true
46-
*/
47-
export function isDevMode(): boolean {
48-
// Check Node.js / Next.js environment variables
49-
if (typeof process !== 'undefined' && process.env) {
50-
if (
51-
process.env.ZG_DEV_MODE === 'true' ||
52-
process.env.ZG_DEV_MODE === '1'
53-
) {
54-
return true
55-
}
56-
if (
57-
process.env.NEXT_PUBLIC_ZG_DEV_MODE === 'true' ||
58-
process.env.NEXT_PUBLIC_ZG_DEV_MODE === '1'
59-
) {
60-
return true
61-
}
62-
}
63-
64-
// Check browser localStorage and URL parameters
65-
if (typeof window !== 'undefined') {
66-
// Check localStorage
67-
try {
68-
const localStorageValue = window.localStorage.getItem('ZG_DEV_MODE')
69-
if (localStorageValue === 'true' || localStorageValue === '1') {
70-
return true
71-
}
72-
} catch {
73-
// localStorage not available
74-
}
75-
76-
// Check URL parameters
77-
try {
78-
const urlParams = new URLSearchParams(window.location.search)
79-
const devParam =
80-
urlParams.get('dev') || urlParams.get('ZG_DEV_MODE')
81-
if (devParam === 'true' || devParam === '1') {
82-
return true
83-
}
84-
} catch {
85-
// URL parsing failed
86-
}
87-
}
88-
89-
return false
19+
// Re-export constants for backward compatibility
20+
export {
21+
TESTNET_CHAIN_ID,
22+
MAINNET_CHAIN_ID,
23+
HARDHAT_CHAIN_ID,
24+
CONTRACT_ADDRESSES,
25+
isDevMode,
9026
}
9127

9228
/**
@@ -229,3 +165,103 @@ export async function createZGComputeNetworkBroker(
229165
throw error
230166
}
231167
}
168+
169+
/**
170+
* Read-only version of ZGComputeNetworkBroker that doesn't require wallet connection.
171+
* Provides access to public blockchain data without authentication.
172+
*
173+
* Use this broker to:
174+
* - Browse available AI providers before connecting wallet
175+
* - Fetch service information and pricing
176+
* - Get provider health metrics
177+
*
178+
* Limitations:
179+
* - Cannot perform authenticated operations (send requests, manage accounts, etc.)
180+
* - No ledger or fine-tuning services (require authentication)
181+
* - Read-only operations only
182+
*/
183+
export class ZGComputeNetworkReadOnlyBroker {
184+
public inference!: ReadOnlyInferenceBroker
185+
186+
constructor(inferenceBroker: ReadOnlyInferenceBroker) {
187+
this.inference = inferenceBroker
188+
}
189+
}
190+
191+
/**
192+
* createZGComputeNetworkReadOnlyBroker creates a read-only broker WITHOUT wallet connection
193+
*
194+
* This broker provides access to public blockchain data (e.g., list providers) without
195+
* requiring user authentication. Perfect for browsing services before connecting a wallet.
196+
*
197+
* @param rpcUrl - JSON-RPC endpoint URL (e.g., 'https://evmrpc-testnet.0g.ai')
198+
* @param chainId - Optional chain ID. If not provided, will be detected from RPC endpoint.
199+
*
200+
* @returns Read-only broker instance with inference.listService() and inference.listServiceWithDetail()
201+
*
202+
* @example
203+
* ```typescript
204+
* // Create read-only broker (no wallet needed!)
205+
* const broker = await createZGComputeNetworkReadOnlyBroker(
206+
* 'https://evmrpc-testnet.0g.ai'
207+
* );
208+
*
209+
* // List all available providers (no authentication required)
210+
* const providers = await broker.inference.listService();
211+
* console.log(`Found ${providers.length} providers`);
212+
*
213+
* // Get detailed provider info with health metrics
214+
* const providersWithHealth = await broker.inference.listServiceWithDetail();
215+
* providersWithHealth.forEach(p => {
216+
* console.log(`${p.provider}: ${p.healthMetrics?.uptime}% uptime`);
217+
* });
218+
* ```
219+
*
220+
* @throws An error if the broker cannot be initialized.
221+
*/
222+
export async function createZGComputeNetworkReadOnlyBroker(
223+
rpcUrl: string,
224+
chainId?: number
225+
): Promise<ZGComputeNetworkReadOnlyBroker> {
226+
try {
227+
// Create provider to detect network if chainId not provided
228+
let detectedChainId = chainId
229+
if (!detectedChainId) {
230+
const provider = new JsonRpcProvider(rpcUrl)
231+
const network = await provider.getNetwork()
232+
detectedChainId = Number(network.chainId)
233+
}
234+
235+
// Log detected network for debugging
236+
const chainIdBigInt = BigInt(detectedChainId)
237+
if (chainIdBigInt === MAINNET_CHAIN_ID) {
238+
console.log(`Detected mainnet (chain ID: ${detectedChainId})`)
239+
} else if (chainIdBigInt === TESTNET_CHAIN_ID) {
240+
if (isDevMode()) {
241+
console.log(
242+
`Detected testnet [DEV MODE] (chain ID: ${detectedChainId})`
243+
)
244+
} else {
245+
console.log(`Detected testnet (chain ID: ${detectedChainId})`)
246+
}
247+
} else if (chainIdBigInt === HARDHAT_CHAIN_ID) {
248+
console.log(`Detected hardhat (chain ID: ${detectedChainId})`)
249+
} else {
250+
console.warn(
251+
`Unknown chain ID: ${detectedChainId}. Using testnet addresses as default.`
252+
)
253+
}
254+
255+
// Create read-only inference broker (no authentication!)
256+
// The broker will auto-detect contract addresses based on chainId
257+
const inferenceBroker = await createReadOnlyInferenceBroker(
258+
rpcUrl,
259+
detectedChainId
260+
)
261+
262+
const broker = new ZGComputeNetworkReadOnlyBroker(inferenceBroker)
263+
return broker
264+
} catch (error) {
265+
throw error
266+
}
267+
}

0 commit comments

Comments
 (0)