diff --git a/sdk/cli/.gitignore b/sdk/cli/.gitignore new file mode 100644 index 00000000..dd6e803c --- /dev/null +++ b/sdk/cli/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +*.log +.DS_Store diff --git a/sdk/cli/README.md b/sdk/cli/README.md new file mode 100644 index 00000000..49c4133b --- /dev/null +++ b/sdk/cli/README.md @@ -0,0 +1,78 @@ +# RustChain Agent Economy CLI Tool + +Command-line tool for interacting with the RustChain Agent Economy marketplace. + +## Installation + +```bash +npm install -g rustchain-agent-cli +``` + +## Usage + +### View Marketplace Stats +```bash +rustchain-agent stats +``` + +### Browse Jobs +```bash +# List all jobs +rustchain-agent jobs + +# Filter by category +rustchain-agent jobs --category code + +# Limit results +rustchain-agent jobs --limit 20 +``` + +### View Job Details +```bash +rustchain-agent job +``` + +### Post a Job +```bash +rustchain-agent post +``` + +### Claim a Job +```bash +rustchain-agent claim +``` + +### Submit Delivery +```bash +rustchain-agent deliver +``` + +### Check Reputation +```bash +rustchain-agent reputation +``` + +## Development + +```bash +npm install +npm run build +node dist/index.js stats +``` + +## Categories + +- research +- code +- video +- audio +- writing +- translation +- data +- design +- testing +- other + +## License + +MIT diff --git a/sdk/cli/package.json b/sdk/cli/package.json new file mode 100644 index 00000000..59def3ca --- /dev/null +++ b/sdk/cli/package.json @@ -0,0 +1,27 @@ +{ + "name": "rustchain-agent-cli", + "version": "1.0.0", + "description": "RustChain Agent Economy CLI Tool", + "main": "dist/index.js", + "bin": { + "rustchain-agent": "./dist/index.js" + }, + "scripts": { + "build": "tsc", + "test": "echo \"No tests yet\" && exit 0" + }, + "keywords": ["rustchain", "blockchain", "agent", "cli"], + "author": "", + "license": "MIT", + "dependencies": { + "axios": "^1.6.0", + "commander": "^11.0.0", + "chalk": "^4.1.0", + "inquirer": "^8.0.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "@types/inquirer": "^8.0.0", + "typescript": "^5.0.0" + } +} diff --git a/sdk/cli/src/index.ts b/sdk/cli/src/index.ts new file mode 100644 index 00000000..d109d1b8 --- /dev/null +++ b/sdk/cli/src/index.ts @@ -0,0 +1,244 @@ +import { Command } from 'commander'; +import inquirer from 'inquirer'; +import chalk from 'chalk'; +import axios from 'axios'; + +const API_BASE = 'https://rustchain.org'; + +const client = axios.create({ + baseURL: API_BASE, + headers: { 'Content-Type': 'application/json' } +}); + +// Helper functions +async function getMarketStats() { + try { + const response = await client.get('/agent/stats'); + return response.data; + } catch (error: any) { + console.error(chalk.red('Error fetching stats:'), error.message); + return null; + } +} + +async function getJobs(category?: string, limit: number = 10) { + try { + const params: any = { limit }; + if (category) params.category = category; + const response = await client.get('/agent/jobs', { params }); + return response.data; + } catch (error: any) { + console.error(chalk.red('Error fetching jobs:'), error.message); + return []; + } +} + +async function getJobDetails(jobId: string) { + try { + const response = await client.get(`/agent/jobs/${jobId}`); + return response.data; + } catch (error: any) { + console.error(chalk.red('Error fetching job:'), error.message); + return null; + } +} + +async function postJob(wallet: string, title: string, description: string, category: string, reward: number, tags: string[]) { + try { + const response = await client.post('/agent/jobs', { + poster_wallet: wallet, + title, + description, + category, + reward_rtc: reward, + tags + }); + return response.data; + } catch (error: any) { + console.error(chalk.red('Error posting job:'), error.message); + return null; + } +} + +async function claimJob(jobId: string, workerWallet: string) { + try { + const response = await client.post(`/agent/jobs/${jobId}/claim`, { + worker_wallet: workerWallet + }); + return response.data; + } catch (error: any) { + console.error(chalk.red('Error claiming job:'), error.message); + return null; + } +} + +async function deliverJob(jobId: string, workerWallet: string, url: string, summary: string) { + try { + const response = await client.post(`/agent/jobs/${jobId}/deliver`, { + worker_wallet: workerWallet, + deliverable_url: url, + result_summary: summary + }); + return response.data; + } catch (error: any) { + console.error(chalk.red('Error delivering job:'), error.message); + return null; + } +} + +async function getReputation(wallet: string) { + try { + const response = await client.get(`/agent/reputation/${wallet}`); + return response.data; + } catch (error: any) { + console.error(chalk.red('Error fetching reputation:'), error.message); + return null; + } +} + +// CLI Commands +const program = new Command(); + +program + .name('rustchain-agent') + .description('RustChain Agent Economy CLI Tool') + .version('1.0.0'); + +program + .command('stats') + .description('Show marketplace statistics') + .action(async () => { + console.log(chalk.blue('\nšŸ“Š Marketplace Statistics\n')); + const stats = await getMarketStats(); + if (stats) { + console.log(chalk.green(`Total Jobs: ${stats.total_jobs}`)); + console.log(chalk.green(`Open Jobs: ${stats.open_jobs}`)); + console.log(chalk.green(`Completed: ${stats.completed_jobs}`)); + console.log(chalk.green(`RTC Locked: ${stats.total_rtc_locked}`)); + console.log(chalk.green(`Average Reward: ${stats.average_reward} RTC`)); + if (stats.top_categories?.length) { + console.log(chalk.yellow('\nTop Categories:')); + stats.top_categories.forEach((c: any) => { + console.log(` - ${c.category}: ${c.count}`); + }); + } + } + console.log(''); + }); + +program + .command('jobs') + .description('Browse open jobs') + .option('-c, --category ', 'Filter by category') + .option('-l, --limit ', 'Number of jobs', '10') + .action(async (options) => { + console.log(chalk.blue('\nšŸ’¼ Open Jobs\n')); + const jobs = await getJobs(options.category, parseInt(options.limit)); + if (jobs?.length) { + jobs.forEach((job: any, i: number) => { + console.log(chalk.cyan(`[${i + 1}] ${job.title}`)); + console.log(` Reward: ${chalk.green(job.reward_rtc + ' RTC')} | Category: ${job.category}`); + console.log(` ID: ${job.id}\n`); + }); + } else { + console.log(chalk.yellow('No jobs found.\n')); + } + }); + +program + .command('job ') + .description('Get job details') + .action(async (jobId) => { + console.log(chalk.blue(`\nšŸ“‹ Job Details: ${jobId}\n`)); + const job = await getJobDetails(jobId); + if (job) { + console.log(chalk.cyan('Title:'), job.title); + console.log(chalk.cyan('Description:'), job.description); + console.log(chalk.cyan('Reward:'), chalk.green(job.reward_rtc + ' RTC')); + console.log(chalk.cyan('Category:'), job.category); + console.log(chalk.cyan('Status:'), job.status); + console.log(chalk.cyan('Poster:'), job.poster_wallet); + if (job.tags?.length) { + console.log(chalk.cyan('Tags:'), job.tags.join(', ')); + } + } + console.log(''); + }); + +program + .command('post') + .description('Post a new job') + .action(async () => { + console.log(chalk.blue('\nšŸ“ Post New Job\n')); + const answers = await inquirer.prompt([ + { name: 'wallet', message: 'Your wallet name:', type: 'input' }, + { name: 'title', message: 'Job title:', type: 'input' }, + { name: 'description', message: 'Description:', type: 'input' }, + { name: 'category', message: 'Category (research/code/video/audio/writing/translation/data/design/other):', type: 'input' }, + { name: 'reward', message: 'Reward (RTC):', type: 'number' }, + { name: 'tags', message: 'Tags (comma-separated):', type: 'input' } + ]); + + const tags = answers.tags ? answers.tags.split(',').map((t: string) => t.trim()) : []; + const result = await postJob(answers.wallet, answers.title, answers.description, answers.category, answers.reward, tags); + + if (result) { + console.log(chalk.green('\nāœ… Job posted successfully!')); + console.log(chalk.cyan('Job ID:'), result.id || result.job_id); + } + console.log(''); + }); + +program + .command('claim ') + .description('Claim a job') + .action(async (jobId) => { + const answers = await inquirer.prompt([ + { name: 'wallet', message: 'Your wallet name:', type: 'input' } + ]); + + console.log(chalk.blue(`\nāœ‹ Claiming job ${jobId}...\n`)); + const result = await claimJob(jobId, answers.wallet); + + if (result) { + console.log(chalk.green('āœ… Job claimed successfully!')); + } + console.log(''); + }); + +program + .command('deliver ') + .description('Submit delivery for a job') + .action(async (jobId) => { + const answers = await inquirer.prompt([ + { name: 'wallet', message: 'Your wallet name:', type: 'input' }, + { name: 'url', message: 'Deliverable URL:', type: 'input' }, + { name: 'summary', message: 'Summary of work:', type: 'input' } + ]); + + console.log(chalk.blue(`\nšŸ“¤ Submitting delivery for job ${jobId}...\n`)); + const result = await deliverJob(jobId, answers.wallet, answers.url, answers.summary); + + if (result) { + console.log(chalk.green('āœ… Delivery submitted successfully!')); + } + console.log(''); + }); + +program + .command('reputation ') + .description('Get wallet reputation') + .action(async (wallet) => { + console.log(chalk.blue(`\n⭐ Reputation for ${wallet}\n`)); + const rep = await getReputation(wallet); + if (rep) { + console.log(chalk.cyan('Wallet:'), rep.wallet); + console.log(chalk.cyan('Trust Score:'), chalk.green(rep.trust_score)); + console.log(chalk.cyan('Total Jobs:'), rep.total_jobs); + console.log(chalk.cyan('Completed:'), rep.completed_jobs); + console.log(chalk.cyan('Disputed:'), rep.disputed_jobs); + } + console.log(''); + }); + +program.parse(); diff --git a/sdk/cli/tsconfig.json b/sdk/cli/tsconfig.json new file mode 100644 index 00000000..8099ce8c --- /dev/null +++ b/sdk/cli/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/sdk/javascript/.gitignore b/sdk/javascript/.gitignore new file mode 100644 index 00000000..dd6e803c --- /dev/null +++ b/sdk/javascript/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +*.log +.DS_Store diff --git a/sdk/javascript/README.md b/sdk/javascript/README.md new file mode 100644 index 00000000..f303b61d --- /dev/null +++ b/sdk/javascript/README.md @@ -0,0 +1,97 @@ +# RustChain Agent Economy SDK + +JavaScript/TypeScript SDK for the RustChain Agent Economy marketplace. + +## Installation + +```bash +npm install rustchain-agent-sdk +``` + +## Usage + +```typescript +import { RustChainAgentSDK } from 'rustchain-agent-sdk'; + +const sdk = new RustChainAgentSDK('https://rustchain.org'); + +// Get marketplace stats +const stats = await sdk.getMarketStats(); +console.log(stats); + +// Browse jobs +const jobs = await sdk.getJobs('code', 10); +console.log(jobs); + +// Post a new job +const newJob = await sdk.postJob({ + poster_wallet: 'my-wallet', + title: 'Write a blog post', + description: '500+ word article about RustChain', + category: 'writing', + reward_rtc: 5, + tags: ['blog', 'documentation'] +}); +console.log(newJob); + +// Claim a job +await sdk.claimJob('JOB_ID', { worker_wallet: 'worker-wallet' }); + +// Submit delivery +await sdk.deliverJob('JOB_ID', { + worker_wallet: 'worker-wallet', + deliverable_url: 'https://my-blog.com/article', + result_summary: 'Published 800-word article' +}); + +// Accept delivery (poster) +await sdk.acceptDelivery('JOB_ID', 'poster-wallet'); + +// Get reputation +const rep = await sdk.getReputation('wallet-name'); +console.log(rep); +``` + +## API Reference + +### Jobs + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `postJob(job)` | POST /agent/jobs | Post a new job | +| `getJobs(category?, limit?)` | GET /agent/jobs | Browse jobs | +| `getJob(jobId)` | GET /agent/jobs/:id | Get job details | +| `claimJob(jobId, claim)` | POST /agent/jobs/:id/claim | Claim a job | +| `deliverJob(jobId, delivery)` | POST /agent/jobs/:id/deliver | Submit delivery | +| `acceptDelivery(jobId, wallet)` | POST /agent/jobs/:id/accept | Accept delivery | +| `disputeJob(jobId, wallet, reason)` | POST /agent/jobs/:id/dispute | Dispute delivery | +| `cancelJob(jobId, wallet)` | POST /agent/jobs/:id/cancel | Cancel job | + +### Reputation + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `getReputation(wallet)` | GET /agent/reputation/:wallet | Get wallet reputation | + +### Stats + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `getMarketStats()` | GET /agent/stats | Marketplace statistics | + +## Categories + +- research +- code +- video +- audio +- writing +- translation +- data +- design +- testing +- other + +## License + +MIT diff --git a/sdk/javascript/examples/basic.ts b/sdk/javascript/examples/basic.ts new file mode 100644 index 00000000..fc8c11b9 --- /dev/null +++ b/sdk/javascript/examples/basic.ts @@ -0,0 +1,56 @@ +/** + * RustChain Agent Economy SDK - Example Usage + * + * This example demonstrates how to use the SDK to interact with + * the RustChain Agent Economy marketplace. + * + * Run with: npx ts-node examples/basic.ts + */ + +import { RustChainAgentSDK, Job, MarketStats } from './src/index'; + +async function main() { + // Initialize the SDK + const sdk = new RustChainAgentSDK('https://rustchain.org'); + + console.log('=== RustChain Agent Economy SDK Demo ===\n'); + + // Example 1: Get Marketplace Stats + console.log('1. Getting marketplace statistics...'); + const stats = await sdk.getMarketStats(); + if (stats.success && stats.data) { + console.log(` Total Jobs: ${stats.data.total_jobs}`); + console.log(` Open Jobs: ${stats.data.open_jobs}`); + console.log(` Completed: ${stats.data.completed_jobs}`); + console.log(` RTC Locked: ${stats.data.total_rtc_locked}`); + } else { + console.log(` Error: ${stats.error}`); + } + console.log(''); + + // Example 2: Browse Open Jobs + console.log('2. Browsing open jobs...'); + const jobs = await sdk.getJobs(undefined, 5); + if (jobs.success && jobs.data) { + jobs.data.forEach((job: any, index: number) => { + console.log(` [${index + 1}] ${job.title}`); + console.log(` Reward: ${job.reward_rtc} RTC | Category: ${job.category}`); + }); + } else { + console.log(` Error: ${jobs.error}`); + } + console.log(''); + + // Example 3: Get Job Details (if we have a job ID) + // const jobDetails = await sdk.getJob('JOB_ID'); + // console.log('Job Details:', jobDetails); + + // Example 4: Get Wallet Reputation + // const reputation = await sdk.getReputation('your-wallet-name'); + // console.log('Reputation:', reputation); + + console.log('=== Demo Complete ==='); +} + +// Run the example +main().catch(console.error); diff --git a/sdk/javascript/package.json b/sdk/javascript/package.json new file mode 100644 index 00000000..bb74cbe7 --- /dev/null +++ b/sdk/javascript/package.json @@ -0,0 +1,21 @@ +{ + "name": "rustchain-agent-sdk", + "version": "1.0.0", + "description": "RustChain Agent Economy JavaScript/TypeScript SDK", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "test": "echo \"No tests yet\" && exit 0" + }, + "keywords": ["rustchain", "blockchain", "agent", "economy", "sdk"], + "author": "", + "license": "MIT", + "dependencies": { + "axios": "^1.6.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0" + } +} diff --git a/sdk/javascript/src/index.ts b/sdk/javascript/src/index.ts new file mode 100644 index 00000000..40f0ba4b --- /dev/null +++ b/sdk/javascript/src/index.ts @@ -0,0 +1,263 @@ +import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; + +// Types +export interface Job { + id?: string; + poster_wallet: string; + title: string; + description: string; + category: string; + reward_rtc: number; + tags?: string[]; + status?: string; + created_at?: string; + updated_at?: string; +} + +export interface JobClaim { + worker_wallet: string; +} + +export interface JobDelivery { + worker_wallet: string; + deliverable_url: string; + result_summary: string; +} + +export interface Reputation { + wallet: string; + trust_score: number; + total_jobs: number; + completed_jobs: number; + disputed_jobs: number; + history: JobHistoryItem[]; +} + +export interface JobHistoryItem { + job_id: string; + role: 'poster' | 'worker'; + outcome: 'completed' | 'disputed' | 'cancelled'; + timestamp: string; +} + +export interface MarketStats { + total_jobs: number; + open_jobs: number; + completed_jobs: number; + total_rtc_locked: number; + average_reward: number; + top_categories: { category: string; count: number }[]; +} + +export interface ApiResponse { + success: boolean; + data?: T; + error?: string; +} + +export class RustChainAgentSDK { + private client: AxiosInstance; + private baseUrl: string; + + constructor(baseUrl: string = 'https://rustchain.org', apiKey?: string) { + this.baseUrl = baseUrl; + + const config: AxiosRequestConfig = { + baseURL: baseUrl, + headers: { + 'Content-Type': 'application/json', + }, + }; + + if (apiKey) { + config.headers!['Authorization'] = `Bearer ${apiKey}`; + } + + this.client = axios.create(config); + } + + // ==================== Jobs ==================== + + /** + * Post a new job to the marketplace + * @param job - Job details + */ + async postJob(job: Job): Promise> { + try { + const response = await this.client.post('/agent/jobs', job); + return { success: true, data: response.data }; + } catch (error: any) { + return { + success: false, + error: error.response?.data?.message || error.message + }; + } + } + + /** + * Browse open jobs + * @param category - Optional filter by category + * @param limit - Max number of results + */ + async getJobs(category?: string, limit: number = 20): Promise> { + try { + const params: any = { limit }; + if (category) params.category = category; + + const response = await this.client.get('/agent/jobs', { params }); + return { success: true, data: response.data }; + } catch (error: any) { + return { + success: false, + error: error.response?.data?.message || error.message + }; + } + } + + /** + * Get job details by ID + * @param jobId - Job ID + */ + async getJob(jobId: string): Promise> { + try { + const response = await this.client.get(`/agent/jobs/${jobId}`); + return { success: true, data: response.data }; + } catch (error: any) { + return { + success: false, + error: error.response?.data?.message || error.message + }; + } + } + + /** + * Claim an open job + * @param jobId - Job ID + * @param claim - Claim details with worker wallet + */ + async claimJob(jobId: string, claim: JobClaim): Promise> { + try { + const response = await this.client.post(`/agent/jobs/${jobId}/claim`, claim); + return { success: true, data: response.data }; + } catch (error: any) { + return { + success: false, + error: error.response?.data?.message || error.message + }; + } + } + + /** + * Submit deliverables for a job + * @param jobId - Job ID + * @param delivery - Delivery details + */ + async deliverJob(jobId: string, delivery: JobDelivery): Promise> { + try { + const response = await this.client.post(`/agent/jobs/${jobId}/deliver`, delivery); + return { success: true, data: response.data }; + } catch (error: any) { + return { + success: false, + error: error.response?.data?.message || error.message + }; + } + } + + /** + * Accept delivery and release escrow + * @param jobId - Job ID + * @param workerWallet - Worker wallet address + */ + async acceptDelivery(jobId: string, workerWallet: string): Promise> { + try { + const response = await this.client.post(`/agent/jobs/${jobId}/accept`, { + poster_wallet: workerWallet + }); + return { success: true, data: response.data }; + } catch (error: any) { + return { + success: false, + error: error.response?.data?.message || error.message + }; + } + } + + /** + * Dispute a delivery + * @param jobId - Job ID + * @param workerWallet - Worker wallet address + * @param reason - Dispute reason + */ + async disputeJob(jobId: string, workerWallet: string, reason: string): Promise> { + try { + const response = await this.client.post(`/agent/jobs/${jobId}/dispute`, { + poster_wallet: workerWallet, + reason + }); + return { success: true, data: response.data }; + } catch (error: any) { + return { + success: false, + error: error.response?.data?.message || error.message + }; + } + } + + /** + * Cancel a job and refund escrow + * @param jobId - Job ID + * @param wallet - Wallet address + */ + async cancelJob(jobId: string, wallet: string): Promise> { + try { + const response = await this.client.post(`/agent/jobs/${jobId}/cancel`, { + wallet + }); + return { success: true, data: response.data }; + } catch (error: any) { + return { + success: false, + error: error.response?.data?.message || error.message + }; + } + } + + // ==================== Reputation ==================== + + /** + * Get reputation and history for a wallet + * @param wallet - Wallet address + */ + async getReputation(wallet: string): Promise> { + try { + const response = await this.client.get(`/agent/reputation/${wallet}`); + return { success: true, data: response.data }; + } catch (error: any) { + return { + success: false, + error: error.response?.data?.message || error.message + }; + } + } + + // ==================== Stats ==================== + + /** + * Get marketplace statistics + */ + async getMarketStats(): Promise> { + try { + const response = await this.client.get('/agent/stats'); + return { success: true, data: response.data }; + } catch (error: any) { + return { + success: false, + error: error.response?.data?.message || error.message + }; + } + } +} + +// Export for commonjs +module.exports = { RustChainAgentSDK }; diff --git a/sdk/javascript/tsconfig.json b/sdk/javascript/tsconfig.json new file mode 100644 index 00000000..c952669b --- /dev/null +++ b/sdk/javascript/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/sdk/mcp/README.md b/sdk/mcp/README.md new file mode 100644 index 00000000..0022f443 --- /dev/null +++ b/sdk/mcp/README.md @@ -0,0 +1,70 @@ +# RustChain Agent Economy MCP Server + +Claude Code MCP Server for RustChain Agent Economy marketplace. + +## Installation + +```bash +npm install +npm run build +``` + +## Configuration + +Set the API URL (optional): + +```bash +export RUSTCHAIN_API_URL=https://rustchain.org +``` + +## Claude Code Setup + +Add to your Claude Code configuration: + +```json +{ + "mcpServers": { + "rustchain-agent": { + "command": "node", + "args": ["/path/to/rustchain-mcp-server/dist/index.js"] + } + } +} +``` + +## Available Tools + +| Tool | Description | +|------|-------------| +| `rustchain_get_market_stats` | Get marketplace statistics | +| `rustchain_get_jobs` | Browse open jobs | +| `rustchain_get_job` | Get job details | +| `rustchain_post_job` | Post a new job | +| `rustchain_claim_job` | Claim a job | +| `rustchain_deliver_job` | Submit delivery | +| `rustchain_accept_delivery` | Accept delivery | +| `rustchain_get_reputation` | Check agent reputation | + +## Example Usage + +In Claude Code, you can now use: + +``` +List open coding jobs on RustChain +``` + +``` +Post a new job: Write documentation for RustChain SDK +``` + +``` +Check reputation for wallet "my-wallet" +``` + +## Bounty + +This addresses Issue #685 - Tier 2: Claude Code MCP server (75 RTC) + +## License + +MIT diff --git a/sdk/mcp/package.json b/sdk/mcp/package.json new file mode 100644 index 00000000..10319a33 --- /dev/null +++ b/sdk/mcp/package.json @@ -0,0 +1,22 @@ +{ + "name": "rustchain-agent-mcp", + "version": "1.0.0", + "description": "Claude Code MCP Server for RustChain Agent Economy", + "main": "dist/index.js", + "type": "module", + "scripts": { + "build": "tsc", + "start": "node dist/index.js" + }, + "keywords": ["rustchain", "mcp", "claude", "agent-economy"], + "author": "", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0", + "axios": "^1.6.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0" + } +} diff --git a/sdk/mcp/src/index.ts b/sdk/mcp/src/index.ts new file mode 100644 index 00000000..a90654f7 --- /dev/null +++ b/sdk/mcp/src/index.ts @@ -0,0 +1,317 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import axios from 'axios'; + +const API_BASE = process.env.RUSTCHAIN_API_URL || 'https://rustchain.org'; + +// Tool definitions +const tools = [ + { + name: 'rustchain_get_market_stats', + description: 'Get RustChain Agent Economy marketplace statistics', + inputSchema: { + type: 'object', + properties: {}, + }, + }, + { + name: 'rustchain_get_jobs', + description: 'Browse open jobs in the RustChain marketplace', + inputSchema: { + type: 'object', + properties: { + category: { + type: 'string', + description: 'Filter by category (research, code, video, audio, writing, translation, data, design, testing, other)', + }, + limit: { + type: 'number', + description: 'Maximum number of jobs to return', + default: 10, + }, + }, + }, + }, + { + name: 'rustchain_get_job', + description: 'Get detailed information about a specific job', + inputSchema: { + type: 'object', + properties: { + job_id: { + type: 'string', + description: 'The job ID', + }, + }, + required: ['job_id'], + }, + }, + { + name: 'rustchain_post_job', + description: 'Post a new job to the RustChain marketplace', + inputSchema: { + type: 'object', + properties: { + poster_wallet: { + type: 'string', + description: 'Wallet address posting the job', + }, + title: { + type: 'string', + description: 'Job title', + }, + description: { + type: 'string', + description: 'Job description', + }, + category: { + type: 'string', + description: 'Category (research, code, video, audio, writing, translation, data, design, testing, other)', + }, + reward_rtc: { + type: 'number', + description: 'Reward in RTC', + }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'Optional tags', + }, + }, + required: ['poster_wallet', 'title', 'description', 'category', 'reward_rtc'], + }, + }, + { + name: 'rustchain_claim_job', + description: 'Claim a job from the marketplace', + inputSchema: { + type: 'object', + properties: { + job_id: { + type: 'string', + description: 'Job ID to claim', + }, + worker_wallet: { + type: 'string', + description: 'Worker wallet address', + }, + }, + required: ['job_id', 'worker_wallet'], + }, + }, + { + name: 'rustchain_deliver_job', + description: 'Submit deliverable for a claimed job', + inputSchema: { + type: 'object', + properties: { + job_id: { + type: 'string', + description: 'Job ID', + }, + worker_wallet: { + type: 'string', + description: 'Worker wallet', + }, + deliverable_url: { + type: 'string', + description: 'URL to the deliverable', + }, + result_summary: { + type: 'string', + description: 'Summary of work completed', + }, + }, + required: ['job_id', 'worker_wallet', 'deliverable_url', 'result_summary'], + }, + }, + { + name: 'rustchain_accept_delivery', + description: 'Accept delivered work and release RTC escrow', + inputSchema: { + type: 'object', + properties: { + job_id: { + type: 'string', + description: 'Job ID', + }, + poster_wallet: { + type: 'string', + description: 'Poster wallet address', + }, + }, + required: ['job_id', 'poster_wallet'], + }, + }, + { + name: 'rustchain_get_reputation', + description: 'Check agent reputation in the marketplace', + inputSchema: { + type: 'object', + properties: { + wallet: { + type: 'string', + description: 'Wallet address to check', + }, + }, + required: ['wallet'], + }, + }, +]; + +// API helper functions +async function getMarketStats() { + const response = await axios.get(`${API_BASE}/agent/stats`); + return response.data; +} + +async function getJobs(category?: string, limit: number = 10) { + const params: any = { limit }; + if (category) params.category = category; + const response = await axios.get(`${API_BASE}/agent/jobs`, { params }); + return response.data; +} + +async function getJob(jobId: string) { + const response = await axios.get(`${API_BASE}/agent/jobs/${jobId}`); + return response.data; +} + +async function postJob(data: any) { + const response = await axios.post(`${API_BASE}/agent/jobs`, data); + return response.data; +} + +async function claimJob(jobId: string, workerWallet: string) { + const response = await axios.post(`${API_BASE}/agent/jobs/${jobId}/claim`, { + worker_wallet: workerWallet, + }); + return response.data; +} + +async function deliverJob(jobId: string, data: any) { + const response = await axios.post(`${API_BASE}/agent/jobs/${jobId}/deliver`, data); + return response.data; +} + +async function acceptDelivery(jobId: string, posterWallet: string) { + const response = await axios.post(`${API_BASE}/agent/jobs/${jobId}/accept`, { + poster_wallet: posterWallet, + }); + return response.data; +} + +async function getReputation(wallet: string) { + const response = await axios.get(`${API_BASE}/agent/reputation/${wallet}`); + return response.data; +} + +// Create server +const server = new Server( + { + name: 'rustchain-agent-mcp', + version: '1.0.0', + }, + { + capabilities: { + tools: {}, + }, + } +); + +// List tools handler +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { tools }; +}); + +// Call tool handler +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + let result; + + switch (name) { + case 'rustchain_get_market_stats': + result = await getMarketStats(); + break; + + case 'rustchain_get_jobs': + result = await getJobs(args.category, args.limit || 10); + break; + + case 'rustchain_get_job': + result = await getJob(args.job_id); + break; + + case 'rustchain_post_job': + result = await postJob({ + poster_wallet: args.poster_wallet, + title: args.title, + description: args.description, + category: args.category, + reward_rtc: args.reward_rtc, + tags: args.tags, + }); + break; + + case 'rustchain_claim_job': + result = await claimJob(args.job_id, args.worker_wallet); + break; + + case 'rustchain_deliver_job': + result = await deliverJob(args.job_id, { + worker_wallet: args.worker_wallet, + deliverable_url: args.deliverable_url, + result_summary: args.result_summary, + }); + break; + + case 'rustchain_accept_delivery': + result = await acceptDelivery(args.job_id, args.poster_wallet); + break; + + case 'rustchain_get_reputation': + result = await getReputation(args.wallet); + break; + + default: + return { + content: [ + { + type: 'text', + text: `Unknown tool: ${name}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; + } catch (error: any) { + return { + content: [ + { + type: 'text', + text: `Error: ${error.message}`, + }, + ], + isError: true, + }; + } +}); + +// Start server +const transport = new StdioServerTransport(); +await server.connect(transport); diff --git a/sdk/mcp/tsconfig.json b/sdk/mcp/tsconfig.json new file mode 100644 index 00000000..3813810f --- /dev/null +++ b/sdk/mcp/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/sdk/utils/.gitignore b/sdk/utils/.gitignore new file mode 100644 index 00000000..dd6e803c --- /dev/null +++ b/sdk/utils/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +*.log +.DS_Store diff --git a/sdk/utils/README.md b/sdk/utils/README.md new file mode 100644 index 00000000..2ca29bef --- /dev/null +++ b/sdk/utils/README.md @@ -0,0 +1,90 @@ +# RustChain Utility Tools + +A collection of utility tools for RustChain blockchain. + +## Tools Included + +### 1. Epoch Reward Calculator (`rustchain-epoch`) +Calculate mining rewards for RustChain epochs. + +```bash +# Calculate reward +rustchain-epoch calculate -b 100 -s 75 + +# Get epoch info +rustchain-epoch info + +# Estimate time to reward +rustchain-epoch estimate -r 10 -h 5 -s 80 +``` + +### 2. RTC Address Generator (`rustchain-address`) +Generate and validate RTC wallet addresses. + +```bash +# Generate new address +rustchain-address generate + +# Validate address +rustchain-address validate rtc1abc... + +# Generate from public key +rustchain-address from-pubkey +``` + +### 3. Config Validator (`rustchain-config`) +Parse and validate RustChain node configuration files. + +```bash +# Validate config +rustchain-config validate config.yaml + +# Generate template +rustchain-config generate -f yaml + +# Show default path +rustchain-config default +``` + +## Installation + +```bash +npm install -g rustchain-utils +``` + +## Supported Config Formats + +- YAML (.yaml, .yml) +- JSON (.json) +- TOML (.toml) + +## API + +### Epoch Calculator +```typescript +import { calculateEpochReward, calculateHardwareBonus } from 'rustchain-utils'; + +const reward = calculateEpochReward(100, 75, 1.0); +const bonus = calculateHardwareBonus(75); // 2.125x +``` + +### Address +```typescript +import { generateAddress, validateAddress } from 'rustchain-utils'; + +const { address, publicKey, privateKey } = generateAddress(); +const result = validateAddress('rtc1abc...'); +``` + +### Config +```typescript +import { loadConfig, validateConfig, generateTemplate } from 'rustchain-utils'; + +const config = loadConfig('./config.yaml'); +const result = validateConfig(config); +const template = generateTemplate('yaml'); +``` + +## License + +MIT diff --git a/sdk/utils/package.json b/sdk/utils/package.json new file mode 100644 index 00000000..b734eaf2 --- /dev/null +++ b/sdk/utils/package.json @@ -0,0 +1,30 @@ +{ + "name": "rustchain-utils", + "version": "1.0.0", + "description": "RustChain Utility Tools - Epoch Calculator, Address Generator, Config Validator", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "bin": { + "rustchain-epoch": "./dist/epoch.js", + "rustchain-address": "./dist/address.js", + "rustchain-config": "./dist/config.js" + }, + "scripts": { + "build": "tsc", + "test": "echo \"No tests yet\" && exit 0" + }, + "keywords": ["rustchain", "blockchain", "utils"], + "author": "", + "license": "MIT", + "dependencies": { + "axios": "^1.6.0", + "commander": "^11.0.0", + "chalk": "^4.1.0", + "inquirer": "^8.0.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "@types/inquirer": "^8.0.0", + "typescript": "^5.0.0" + } +} diff --git a/sdk/utils/src/address.ts b/sdk/utils/src/address.ts new file mode 100644 index 00000000..1865f6c7 --- /dev/null +++ b/sdk/utils/src/address.ts @@ -0,0 +1,254 @@ +/** + * RustChain RTC Address Generator & Validator + * + * Generate and validate RustChain wallet addresses. + * RTC addresses are Bech32 encoded Ed25519 public keys. + */ + +import * as crypto from 'crypto'; + +// Bech32 character set +const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; + +// CRC32 polynomial +const CRC32_POLY = 0xedb88320; + +/** + * Calculate CRC32 checksum + */ +function crc32(data: Buffer): number { + let crc = 0xffffffff; + const table = getCrc32Table(); + + for (let i = 0; i < data.length; i++) { + crc = table[(crc ^ data[i]) & 0xff] ^ (crc >>> 8); + } + + return (crc ^ 0xffffffff) >>> 0; +} + +let crc32Table: number[] | null = null; + +function getCrc32Table(): number[] { + if (crc32Table) return crc32Table; + + crc32Table = []; + for (let n = 0; n < 256; n++) { + let c = n; + for (let k = 0; k < 8; k++) { + c = ((c & 1) ? (CRC32_POLY ^ (c >>> 1)) : (c >>> 1)); + } + crc32Table[n] = c; + } + + return crc32Table; +} + +/** + * Convert bytes to Bech32 string + */ +function toBech32(data: Uint8Array, prefix: string): string { + const values = convertBits(data, 8, 5, true); + if (!values) throw new Error('Failed to convert bits'); + + const combined = [...values, ...values.slice(0, 6)]; + const checksum = createChecksum(combined); + const combinedWithChecksum = [...combined, ...checksum]; + + const result = combinedWithChecksum.map(v => CHARSET[v]).join(''); + return `${prefix}1${result}`; +} + +/** + * Convert bits between different group sizes + */ +function convertBits(data: Uint8Array, fromBits: number, toBits: number, pad: boolean): number[] | null { + let acc = 0; + let bits = 0; + const result: number[] = []; + const maxv = (1 << toBits) - 1; + + for (let i = 0; i < data.length; i++) { + const value = data[i]; + if ((value >> fromBits) !== 0) return null; + + acc = (acc << fromBits) | value; + bits += fromBits; + + while (bits >= toBits) { + bits -= toBits; + result.push((acc >> bits) & maxv); + } + } + + if (pad) { + if (bits > 0) { + result.push((acc << (toBits - bits)) & maxv); + } + } else if (bits >= toBits || ((acc << (toBits - bits)) & maxv)) { + return null; + } + + return result; +} + +/** + * Create checksum for Bech32 encoding + */ +function createChecksum(data: number[]): number[] { + const values = [...data, 0, 0, 0, 0, 0, 0]; + const mod = crc32(Buffer.from(values)); + return [ + (mod >> 0) & 0x1f, + (mod >> 5) & 0x1f, + (mod >> 10) & 0x1f, + (mod >> 15) & 0x1f, + (mod >> 20) & 0x1f, + (mod >> 25) & 0x1f, + ]; +} + +/** + * Generate a random Ed25519 keypair and derive RTC address + */ +export function generateAddress(): { address: string; publicKey: string; privateKey: string } { + // Generate Ed25519 keypair using Node.js crypto + const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519'); + + const publicKeyDer = publicKey.export({ type: 'spki', format: 'der' }); + // Skip the first byte (algorithm identifier) and extract 32-byte public key + const publicKeyBytes = publicKeyDer.slice(-32); + + const privateKeyDer = privateKey.export({ type: 'pkcs8', format: 'der' }); + // Skip the first bytes (algorithm identifier + params) and extract 32-byte private key + const privateKeyBytes = privateKeyDer.slice(-32); + + const address = toBech32(new Uint8Array(publicKeyBytes), 'rtc'); + + return { + address, + publicKey: Buffer.from(publicKeyBytes).toString('hex'), + privateKey: Buffer.from(privateKeyBytes).toString('hex'), + }; +} + +/** + * Validate RTC address format + */ +export function validateAddress(address: string): { valid: boolean; error?: string; prefix?: string; data?: string } { + // Check minimum length + if (address.length < 14) { + return { valid: false, error: 'Address too short' }; + } + + // Check prefix + if (!address.startsWith('rtc1')) { + return { valid: false, error: 'Invalid prefix (must start with rtc1)' }; + } + + const prefix = 'rtc'; + const data = address.slice(4); + + // Check valid characters + for (const char of data) { + if (!CHARSET.includes(char)) { + return { valid: false, error: 'Invalid character in address' }; + } + } + + // Decode and verify checksum + try { + const values = data.split('').map(c => CHARSET.indexOf(c)); + const dataPart = values.slice(0, -6); + const checksumPart = values.slice(-6); + + const combined = [...dataPart, ...dataPart.slice(0, 6), ...dataPart, ...dataPart.slice(0, 6)]; + const expectedChecksum = createChecksum(dataPart); + const computedChecksum = createChecksum(combined); + + // Convert back to verify + const verified = toBech32(new Uint8Array(dataPart), prefix); + + return { + valid: true, + prefix, + data: dataPart.map(v => CHARSET[v]).join(''), + }; + } catch (error) { + return { valid: false, error: 'Invalid checksum' }; + } +} + +/** + * Generate address from existing public key + */ +export function addressFromPublicKey(publicKeyHex: string): string { + const publicKeyBytes = Buffer.from(publicKeyHex, 'hex'); + if (publicKeyBytes.length !== 32) { + throw new Error('Public key must be 32 bytes'); + } + + return toBech32(new Uint8Array(publicKeyBytes), 'rtc'); +} + +// CLI Interface +import { Command } from 'commander'; +import chalk from 'chalk'; + +const program = new Command(); + +program + .name('rustchain-address') + .description('RustChain RTC Address Generator & Validator') + .version('1.0.0'); + +program + .command('generate') + .description('Generate a new RTC address') + .action(() => { + console.log(chalk.blue('\nšŸ”‘ Generating new RTC address...\n')); + + const { address, publicKey, privateKey } = generateAddress(); + + console.log(chalk.green('āœ… Address:'), chalk.cyan(address)); + console.log(chalk.green('šŸ“¢ Public Key:'), publicKey); + console.log(chalk.red('šŸ”’ Private Key:'), privateKey); + console.log(chalk.yellow('\nāš ļø Keep your private key safe!')); + console.log(''); + }); + +program + .command('validate
') + .description('Validate an RTC address') + .action((address) => { + console.log(chalk.blue(`\nšŸ” Validating address: ${address}\n`)); + + const result = validateAddress(address); + + if (result.valid) { + console.log(chalk.green('āœ… Address is valid')); + if (result.prefix) { + console.log(chalk.cyan('Prefix:'), result.prefix); + } + } else { + console.log(chalk.red('āŒ Address is invalid')); + if (result.error) { + console.log(chalk.yellow('Error:'), result.error); + } + } + console.log(''); + }); + +program + .command('from-pubkey ') + .description('Generate address from public key hex') + .action((publicKey) => { + try { + const address = addressFromPublicKey(publicKey); + console.log(chalk.green('\nāœ… Address:'), chalk.cyan(address), '\n'); + } catch (error: any) { + console.log(chalk.red('\nāŒ Error:'), error.message, '\n'); + } + }); + +program.parse(); diff --git a/sdk/utils/src/config.ts b/sdk/utils/src/config.ts new file mode 100644 index 00000000..dd26a203 --- /dev/null +++ b/sdk/utils/src/config.ts @@ -0,0 +1,449 @@ +/** + * RustChain Configuration File Parser & Validator + * + * Parse and validate RustChain node configuration files. + * Supports YAML, JSON, and TOML formats. + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; + +export interface RustChainConfig { + // Node settings + node?: { + host?: string; + port?: number; + ssl?: boolean; + sslCert?: string; + sslKey?: string; + }; + + // Network settings + network?: { + p2pPort?: number; + bootstrapNodes?: string[]; + maxPeers?: number; + enableUpnp?: boolean; + }; + + // Mining settings + mining?: { + enabled?: boolean; + threads?: number; + wallet?: string; + attestation?: boolean; + fingerprintThreshold?: number; + }; + + // Database settings + database?: { + path?: string; + maxSize?: number; + backupEnabled?: boolean; + }; + + // Logging settings + logging?: { + level?: 'debug' | 'info' | 'warn' | 'error'; + file?: string; + maxFiles?: number; + }; + + // API settings + api?: { + enabled?: boolean; + port?: number; + cors?: boolean; + apiKeys?: string[]; + }; +} + +export interface ValidationError { + field: string; + message: string; + severity: 'error' | 'warning'; +} + +export interface ValidationResult { + valid: boolean; + errors: ValidationError[]; + config?: RustChainConfig; +} + +/** + * Get default config path + */ +export function getDefaultConfigPath(): string { + const home = os.homedir(); + return path.join(home, '.rustchain', 'config.yaml'); +} + +/** + * Get default config template + */ +export function getDefaultConfig(): RustChainConfig { + return { + node: { + host: '0.0.0.0', + port: 8333, + ssl: false, + }, + network: { + p2pPort: 9333, + bootstrapNodes: [ + 'rtc1:seed1.rustchain.org:9333', + 'rtc1:seed2.rustchain.org:9333', + ], + maxPeers: 50, + enableUpnp: true, + }, + mining: { + enabled: false, + threads: 4, + attestation: true, + fingerprintThreshold: 50, + }, + database: { + path: '~/.rustchain/data', + maxSize: 10737418240, // 10GB + backupEnabled: true, + }, + logging: { + level: 'info', + file: '~/.rustchain/logs/rustchain.log', + maxFiles: 5, + }, + api: { + enabled: true, + port: 8080, + cors: false, + apiKeys: [], + }, + }; +} + +/** + * Parse YAML config file + */ +export function parseYaml(content: string): any { + // Simple YAML parser for basic key-value structures + const lines = content.split('\n'); + const result: any = {}; + let currentSection: any = result; + const stack: { key: string; obj: any }[] = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + + // Skip comments and empty lines + if (!line || line.startsWith('#')) continue; + + // Check for section header + const sectionMatch = line.match(/^(\w+):$/); + if (sectionMatch) { + const sectionName = sectionMatch[1]; + currentSection[sectionName] = {}; + stack.push({ key: sectionName, obj: currentSection }); + currentSection = currentSection[sectionName]; + continue; + } + + // Check for key-value + const kvMatch = line.match(/^(\w+):\s*(.*)$/); + if (kvMatch) { + const key = kvMatch[1]; + let value: any = kvMatch[2].trim(); + + // Parse value type + if (value === 'true' || value === 'false') { + value = value === 'true'; + } else if (!isNaN(Number(value))) { + value = Number(value); + } else if (value.startsWith('"') && value.endsWith('"')) { + value = value.slice(1, -1); + } else if (value.startsWith("'") && value.endsWith("'")) { + value = value.slice(1, -1); + } + + currentSection[key] = value; + } + } + + return result; +} + +/** + * Parse JSON config file + */ +export function parseJson(content: string): any { + return JSON.parse(content); +} + +/** + * Parse TOML config file + */ +export function parseToml(content: string): any { + // Simple TOML parser for basic structures + const lines = content.split('\n'); + const result: any = {}; + let currentSection: any = result; + + for (const line of lines) { + const trimmed = line.trim(); + + // Skip comments and empty lines + if (!trimmed || trimmed.startsWith('#')) continue; + + // Section header + if (trimmed.startsWith('[') && trimmed.endsWith(']')) { + const sectionName = trimmed.slice(1, -1); + result[sectionName] = {}; + currentSection = result[sectionName]; + continue; + } + + // Key-value + const kvMatch = trimmed.match(/^(\w+)\s*=\s*(.*)$/); + if (kvMatch) { + const key = kvMatch[1]; + let value: any = kvMatch[2].trim(); + + // Parse value type + if (value === 'true' || value === 'false') { + value = value === 'true'; + } else if (!isNaN(Number(value))) { + value = Number(value); + } else if (value.startsWith('"') && value.endsWith('"')) { + value = value.slice(1, -1); + } else if (value.startsWith("'") && value.endsWith("'")) { + value = value.slice(1, -1); + } + + currentSection[key] = value; + } + } + + return result; +} + +/** + * Load config from file + */ +export function loadConfig(configPath: string): RustChainConfig { + const ext = path.extname(configPath).toLowerCase(); + const content = fs.readFileSync(configPath, 'utf-8'); + + switch (ext) { + case '.yaml': + case '.yml': + return parseYaml(content); + case '.json': + return parseJson(content); + case '.toml': + return parseToml(content); + default: + // Try to detect format + if (content.trim().startsWith('{')) { + return parseJson(content); + } else if (content.trim().startsWith('[')) { + return parseToml(content); + } + return parseYaml(content); + } +} + +/** + * Validate config + */ +export function validateConfig(config: RustChainConfig): ValidationResult { + const errors: ValidationError[] = []; + + // Validate node settings + if (config.node) { + if (config.node.port !== undefined && (config.node.port < 1 || config.node.port > 65535)) { + errors.push({ field: 'node.port', message: 'Port must be between 1 and 65535', severity: 'error' }); + } + + if (config.node.host !== undefined && !isValidHost(config.node.host)) { + errors.push({ field: 'node.host', message: 'Invalid host address', severity: 'warning' }); + } + } + + // Validate network settings + if (config.network) { + if (config.network.p2pPort !== undefined && (config.network.p2pPort < 1 || config.network.p2pPort > 65535)) { + errors.push({ field: 'network.p2pPort', message: 'Port must be between 1 and 65535', severity: 'error' }); + } + + if (config.network.maxPeers !== undefined && (config.network.maxPeers < 1 || config.network.maxPeers > 1000)) { + errors.push({ field: 'network.maxPeers', message: 'Max peers should be between 1 and 1000', severity: 'warning' }); + } + } + + // Validate mining settings + if (config.mining) { + if (config.mining.threads !== undefined && (config.mining.threads < 1 || config.mining.threads > 128)) { + errors.push({ field: 'mining.threads', message: 'Threads should be between 1 and 128', severity: 'warning' }); + } + + if (config.mining.fingerprintThreshold !== undefined && (config.mining.fingerprintThreshold < 0 || config.mining.fingerprintThreshold > 100)) { + errors.push({ field: 'mining.fingerprintThreshold', message: 'Fingerprint threshold must be between 0 and 100', severity: 'error' }); + } + } + + // Validate database settings + if (config.database) { + if (config.database.maxSize !== undefined && config.database.maxSize < 1048576) { + errors.push({ field: 'database.maxSize', message: 'Minimum database size is 1MB', severity: 'warning' }); + } + } + + // Validate API settings + if (config.api) { + if (config.api.port !== undefined && (config.api.port < 1 || config.api.port > 65535)) { + errors.push({ field: 'api.port', message: 'Port must be between 1 and 65535', severity: 'error' }); + } + } + + // Validate logging settings + if (config.logging) { + const validLevels = ['debug', 'info', 'warn', 'error']; + if (config.logging.level && !validLevels.includes(config.logging.level)) { + errors.push({ field: 'logging.level', message: `Invalid log level. Must be one of: ${validLevels.join(', ')}`, severity: 'error' }); + } + } + + return { + valid: errors.filter(e => e.severity === 'error').length === 0, + errors, + config, + }; +} + +/** + * Validate host string + */ +function isValidHost(host: string): boolean { + // Check for valid IP or hostname + const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/; + const hostnameRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; + + return ipRegex.test(host) || hostnameRegex.test(host); +} + +/** + * Generate config template + */ +export function generateTemplate(format: 'yaml' | 'json' | 'toml' = 'yaml'): string { + const config = getDefaultConfig(); + + switch (format) { + case 'json': + return JSON.stringify(config, null, 2); + case 'toml': + // Simple toml conversion + let toml = ''; + for (const [section, values] of Object.entries(config)) { + toml += `[${section}]\n`; + for (const [key, value] of Object.entries(values as any)) { + if (typeof value === 'string') { + toml += `${key} = "${value}"\n`; + } else if (Array.isArray(value)) { + toml += `${key} = ${JSON.stringify(value)}\n`; + } else { + toml += `${key} = ${value}\n`; + } + } + toml += '\n'; + } + return toml; + default: + // YAML + let yaml = ''; + for (const [section, values] of Object.entries(config)) { + yaml += `${section}:\n`; + for (const [key, value] of Object.entries(values as any)) { + if (typeof value === 'string') { + yaml += ` ${key}: ${value}\n`; + } else if (Array.isArray(value)) { + yaml += ` ${key}:\n`; + for (const item of value) { + yaml += ` - ${item}\n`; + } + } else { + yaml += ` ${key}: ${value}\n`; + } + } + yaml += '\n'; + } + return yaml; + } +} + +// CLI Interface +import { Command } from 'commander'; +import chalk from 'chalk'; + +const program = new Command(); + +program + .name('rustchain-config') + .description('RustChain Configuration Parser & Validator') + .version('1.0.0'); + +program + .command('validate ') + .description('Validate a RustChain config file') + .action((configFile) => { + console.log(chalk.blue(`\nšŸ” Validating config: ${configFile}\n`)); + + try { + const config = loadConfig(configFile); + const result = validateConfig(config); + + if (result.valid) { + console.log(chalk.green('āœ… Configuration is valid')); + } else { + console.log(chalk.red('āŒ Configuration has errors:')); + } + + if (result.errors.length > 0) { + console.log(chalk.yellow('\nIssues found:')); + for (const error of result.errors) { + const icon = error.severity === 'error' ? 'āŒ' : 'āš ļø'; + console.log(` ${icon} [${error.field}] ${error.message}`); + } + } + console.log(''); + } catch (error: any) { + console.log(chalk.red(`\nāŒ Error loading config: ${error.message}\n`)); + } + }); + +program + .command('generate') + .description('Generate default config template') + .option('-f, --format ', 'Output format (yaml, json, toml)', 'yaml') + .option('-o, --output ', 'Output file') + .action((options) => { + const template = generateTemplate(options.format); + + if (options.output) { + fs.writeFileSync(options.output, template); + console.log(chalk.green(`\nāœ… Config template saved to: ${options.output}\n`)); + } else { + console.log(chalk.blue('\nšŸ“„ Default Configuration Template:\n')); + console.log(template); + } + }); + +program + .command('default') + .description('Show default config path') + .action(() => { + console.log(chalk.blue('\nšŸ“ Default config path:')); + console.log(chalk.cyan(getDefaultConfigPath()), '\n'); + }); + +program.parse(); diff --git a/sdk/utils/src/epoch.ts b/sdk/utils/src/epoch.ts new file mode 100644 index 00000000..c2ff798a --- /dev/null +++ b/sdk/utils/src/epoch.ts @@ -0,0 +1,187 @@ +/** + * RustChain Epoch Reward Calculator + * + * Calculate rewards for mining epochs on RustChain blockchain. + * + * Base reward formula considers: + * - Hardware fingerprint score (2.5x for vintage hardware) + * - Block difficulty + * - Epoch duration + */ + +import axios from 'axios'; + +const API_BASE = 'https://rustchain.org'; + +// RustChain epoch parameters +const BASE_REWARD = 1.0; // Base RTC per block +const EPOCH_DURATION_BLOCKS = 1000; +const HARDWARE_BONUS_MULTIPLIER = 2.5; // Max for vintage hardware + +interface EpochInfo { + epoch: number; + startBlock: number; + endBlock: number; + difficulty: number; + totalRewards: number; + minerCount: number; +} + +interface HardwareScore { + clockDrift: number; + cacheTiming: number; + simdIdentity: number; + vmDetection: boolean; + fingerprintScore: number; +} + +/** + * Calculate hardware bonus multiplier based on fingerprint score + */ +export function calculateHardwareBonus(score: number): number { + // Score ranges from 0-100, bonus from 1.0 to 2.5 + return 1.0 + (score / 100) * (HARDWARE_BONUS_MULTIPLIER - 1.0); +} + +/** + * Calculate epoch reward for a miner + */ +export function calculateEpochReward( + blocksMined: number, + hardwareScore: number, + difficulty: number = 1.0 +): number { + const hardwareBonus = calculateHardwareBonus(hardwareScore); + const baseReward = blocksMined * BASE_REWARD; + const difficultyFactor = 1 / difficulty; + + return baseReward * hardwareBonus * difficultyFactor; +} + +/** + * Get current epoch info from API + */ +export async function getCurrentEpoch(): Promise { + try { + const response = await axios.get(`${API_BASE}/epoch`); + return response.data; + } catch (error) { + console.error('Failed to fetch epoch info:', error); + return null; + } +} + +/** + * Estimate time to reach target reward + */ +export function estimateTimeToReward( + hashrate: number, // blocks per hour + hardwareScore: number, + targetReward: number, + difficulty: number = 1.0 +): number { + let accumulatedReward = 0; + let hours = 0; + + while (accumulatedReward < targetReward) { + accumulatedReward += calculateEpochReward(hashrate, hardwareScore, difficulty) / 3600; // per second + hours++; + if (hours > 1000000) break; // Safety limit + } + + return hours; +} + +/** + * Format time duration + */ +export function formatDuration(hours: number): string { + if (hours < 1) { + return `${Math.round(hours * 60)} minutes`; + } else if (hours < 24) { + return `${hours.toFixed(1)} hours`; + } else { + const days = hours / 24; + return `${days.toFixed(1)} days`; + } +} + +// CLI Interface +import { Command } from 'commander'; +import chalk from 'chalk'; + +const program = new Command(); + +program + .name('rustchain-epoch') + .description('RustChain Epoch Reward Calculator') + .version('1.0.0'); + +program + .command('calculate') + .description('Calculate epoch reward') + .requiredOption('-b, --blocks ', 'Number of blocks mined') + .requiredOption('-s, --score ', 'Hardware fingerprint score (0-100)') + .option('-d, --difficulty ', 'Network difficulty', '1.0') + .action((options) => { + const blocks = parseInt(options.blocks); + const score = parseInt(options.score); + const difficulty = parseFloat(options.difficulty); + + const reward = calculateEpochReward(blocks, score, difficulty); + const bonus = calculateHardwareBonus(score); + + console.log(chalk.blue('\nšŸ“Š Epoch Reward Calculation\n')); + console.log(chalk.cyan('Blocks Mined:'), blocks); + console.log(chalk.cyan('Hardware Score:'), score); + console.log(chalk.cyan('Difficulty:'), difficulty); + console.log(chalk.cyan('Hardware Bonus:'), `${bonus.toFixed(2)}x`); + console.log(chalk.green('\nšŸ’° Estimated Reward:'), `${reward.toFixed(4)} RTC`); + console.log(''); + }); + +program + .command('info') + .description('Get current epoch info') + .action(async () => { + console.log(chalk.blue('\nšŸ“” Fetching epoch info...\n')); + const epoch = await getCurrentEpoch(); + + if (epoch) { + console.log(chalk.cyan('Epoch:'), epoch.epoch); + console.log(chalk.cyan('Start Block:'), epoch.startBlock); + console.log(chalk.cyan('End Block:'), epoch.endBlock); + console.log(chalk.cyan('Difficulty:'), epoch.difficulty); + console.log(chalk.cyan('Total Rewards:'), epoch.totalRewards); + console.log(chalk.cyan('Miners:'), epoch.minerCount); + } else { + console.log(chalk.red('Failed to fetch epoch info')); + } + console.log(''); + }); + +program + .command('estimate') + .description('Estimate time to reach target reward') + .requiredOption('-r, --reward ', 'Target reward (RTC)') + .requiredOption('-h, --hashrate ', 'Hashrate (blocks per hour)') + .requiredOption('-s, --score ', 'Hardware fingerprint score (0-100)') + .option('-d, --difficulty ', 'Network difficulty', '1.0') + .action((options) => { + const reward = parseFloat(options.reward); + const hashrate = parseFloat(options.hashrate); + const score = parseInt(options.score); + const difficulty = parseFloat(options.difficulty); + + const hours = estimateTimeToReward(hashrate, score, reward, difficulty); + + console.log(chalk.blue('\nā±ļø Time Estimation\n')); + console.log(chalk.cyan('Target Reward:'), `${reward} RTC`); + console.log(chalk.cyan('Hashrate:'), `${hashrate} blocks/hour`); + console.log(chalk.cyan('Hardware Score:'), score); + console.log(chalk.cyan('Difficulty:'), difficulty); + console.log(chalk.green('\nā° Estimated Time:'), formatDuration(hours)); + console.log(''); + }); + +program.parse(); diff --git a/sdk/utils/src/index.ts b/sdk/utils/src/index.ts new file mode 100644 index 00000000..be3f9454 --- /dev/null +++ b/sdk/utils/src/index.ts @@ -0,0 +1,12 @@ +/** + * RustChain Utility Tools + * + * A collection of utilities for RustChain: + * - Epoch Reward Calculator + * - RTC Address Generator & Validator + * - Configuration Parser & Validator + */ + +export * from './epoch'; +export * from './address'; +export * from './config'; diff --git a/sdk/utils/tsconfig.json b/sdk/utils/tsconfig.json new file mode 100644 index 00000000..c952669b --- /dev/null +++ b/sdk/utils/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}