// NeuraMesh SDK — Main Client import type { NeuraMeshConfig, ProtocolState, ComputeNode, ComputeJob, CreateJobParams, RegisterNodeParams, } from "./types"; import { JobStatus } from "./types"; import { WalrusIntegration } from "./walrus-integration"; const TESTNET_CONFIG: NeuraMeshConfig = { network: "testnet", packageId: "0x_PACKAGE_ID_AFTER_DEPLOY", protocolStateId: "0x_PROTOCOL_STATE_ID", walrusAggregatorUrl: "https://aggregator.testnet.walrus.space", walrusPublisherUrl: "https://publisher.testnet.walrus.space", }; export class NeuraMeshClient { private config: NeuraMeshConfig; private rpcUrl: string; public walrus: WalrusIntegration; constructor(config: Partial = {}) { this.config = { ...TESTNET_CONFIG, ...config }; this.rpcUrl = this.getRpcUrl(); this.walrus = new WalrusIntegration(this.config.walrusPublisherUrl, this.config.walrusAggregatorUrl); } async getProtocolState(): Promise { const obj = await this.getObject(this.config.protocolStateId); const f = obj?.data?.content?.fields || {}; return { id: this.config.protocolStateId, totalNodes: Number(f.total_nodes || 0), totalJobs: Number(f.total_jobs || 0), totalCompleted: Number(f.total_completed || 0), feeBps: Number(f.fee_bps || 250), minStake: BigInt(f.min_stake || "1000000000"), treasury: BigInt(f.treasury || "0"), version: Number(f.version || 1), }; } buildCreateJobTx(params: CreateJobParams): object { return { packageObjectId: this.config.packageId, module: "marketplace", function: "create_job", typeArguments: [], arguments: [ this.config.protocolStateId, "0x6", Array.from(new TextEncoder().encode(params.description)), params.computeType, params.minGpuTier, Array.from(new TextEncoder().encode(params.modelBlobId)), Array.from(new TextEncoder().encode(params.inputBlobId)), params.maxDurationMs ?? 3600000, ], gasBudget: 100_000_000, }; } async getRecentJobs(limit = 50): Promise { const events = await this.queryEvents("marketplace", "JobCreated", limit); return events.map((e: any) => this.parseJobEvent(e)); } async getJob(jobId: string): Promise { const obj = await this.getObject(jobId); if (!obj?.data?.content?.fields) return null; return this.parseJobObj(obj.data.content.fields, jobId); } buildRegisterNodeTx(params: RegisterNodeParams): object { return { packageObjectId: this.config.packageId, module: "registry", function: "register_node", typeArguments: [], arguments: [ this.config.protocolStateId, "0x6", Array.from(new TextEncoder().encode(params.name)), Array.from(new TextEncoder().encode(params.gpuModel)), params.vramGb, params.cpuCores, params.ramGb, params.gpuTier, params.supportedTypes, Array.from(new TextEncoder().encode(params.endpoint)), Array.from(new TextEncoder().encode(params.capabilityBlobId)), ], gasBudget: 100_000_000, }; } async getNode(nodeId: string): Promise { const obj = await this.getObject(nodeId); if (!obj?.data?.content?.fields) return null; return this.parseNodeObj(obj.data.content.fields, nodeId); } async getNodesByOwner(owner: string): Promise { const result = await this.rpcCall("suix_getOwnedObjects", [ owner, { filter: { StructType: `${this.config.packageId}::registry::ComputeNode` }, options: { showContent: true, showType: true }, }, ]); return (result?.data || []) .filter((o: any) => o?.data?.content?.fields) .map((o: any) => this.parseNodeObj(o.data.content.fields, o.data.objectId)); } async uploadModel(file: File, meta: Record) { return this.walrus.storeModelWeights(file, meta); } async uploadInput(data: Uint8Array | File) { return this.walrus.storeBlob(data instanceof File ? data : data); } async downloadResult(blobId: string) { return this.walrus.retrieveBlob(blobId); } private async rpcCall(method: string, params: any[]): Promise { const r = await fetch(this.rpcUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }), }); const j = await r.json(); if (j.error) throw new Error(`RPC: ${JSON.stringify(j.error)}`); return j.result; } private async getObject(id: string) { return this.rpcCall("sui_getObject", [id, { showContent: true, showOwner: true, showType: true }]); } private async queryEvents(mod: string, evt: string, limit: number) { const r = await this.rpcCall("suix_queryEvents", [ { MoveEventType: `${this.config.packageId}::${mod}::${evt}` }, null, limit, true, ]); return r?.data || []; } private parseJobEvent(e: any): ComputeJob { const p = e?.parsedJson || {}; return { id: p.job_id || "", creator: p.creator || "", description: "", computeType: Number(p.compute_type || 0), minGpuTier: Number(p.min_gpu_tier || 0), modelBlobId: p.model_blob_id || "", inputBlobId: p.input_blob_id || "", resultBlobId: "", paymentAmount: BigInt(p.payment_amount || "0"), status: JobStatus.Open, assignedNode: null, createdAt: Date.now(), deadline: Number(p.deadline || 0), completedAt: 0, maxDurationMs: 3600000, }; } private parseJobObj(f: any, id: string): ComputeJob { return { id, creator: f.creator || "", description: f.description || "", computeType: Number(f.compute_type || 0), minGpuTier: Number(f.min_gpu_tier || 0), modelBlobId: f.model_blob_id || "", inputBlobId: f.input_blob_id || "", resultBlobId: f.result_blob_id || "", paymentAmount: BigInt(f.payment || "0"), status: Number(f.status || 0), assignedNode: f.assigned_node?.Some || null, createdAt: Number(f.created_at || 0), deadline: Number(f.deadline || 0), completedAt: Number(f.completed_at || 0), maxDurationMs: Number(f.max_duration_ms || 0), }; } private parseNodeObj(f: any, id: string): ComputeNode { return { id, owner: f.owner, name: f.name, gpuModel: f.gpu_model, vramGb: Number(f.vram_gb), cpuCores: Number(f.cpu_cores), ramGb: Number(f.ram_gb), gpuTier: Number(f.gpu_tier), supportedTypes: Number(f.supported_types), endpoint: f.endpoint, stakeAmount: BigInt(f.stake || "0"), status: Number(f.status), activeJobs: Number(f.active_jobs), jobsCompleted: Number(f.jobs_completed), jobsFailed: Number(f.jobs_failed), reputation: Number(f.reputation), registeredAt: Number(f.registered_at), lastActive: Number(f.last_active), capabilityBlobId: f.capability_blob_id, }; } private getRpcUrl(): string { const urls: Record = { mainnet: "https://fullnode.mainnet.sui.io:443", testnet: "https://fullnode.testnet.sui.io:443", devnet: "https://fullnode.devnet.sui.io:443", localnet: "http://localhost:9000", }; return urls[this.config.network] || urls.testnet; } }