posture: 110010
dims: protection delegation computation
by: keeper
Build Guide
Step-by-step implementation from zero to production
Overview
This guide walks through building the complete Proverb Revelation Protocol in 4 phases:
- Foundation (Week 1): Zcash + Database
- Backend (Week 2): TEE + AI Integration
- Frontend (Week 3): User Interface
- Production (Week 4): Testing + Deployment
Phase 1: Foundation (Week 1)
Goals
- Zcash light client syncing
- PostgreSQL operational
- Basic monitoring working
- Memo parsing functional
Step 1.1: Initialize Oracle Swordsman Project
cd ~/proverb-protocol/oracle-swordsman
# For TypeScript
npm init -y
npm install --save \
axios \
pg \
dotenv \
winston \
express
# Note: Nillion SDK is optional (use --omit=optional if needed)
# npm install --save @nillion/client-web
npm install --save-dev \
@types/node \
@types/pg \
typescript \
ts-node \
nodemon
# Create tsconfig.json
cat > tsconfig.json << 'EOF'
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
EOF
# Create package.json scripts
cat > package.json << 'EOF'
{
"name": "oracle-swordsman",
"version": "1.0.0",
"scripts": {
"dev": "nodemon src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"test": "ts-node src/test.ts"
}
}
EOF
Step 1.2: Create Configuration Module
// src/config.ts
import dotenv from 'dotenv';
dotenv.config();
export const config = {
// Nillion
nillion: {
apiKey: process.env.NILLION_API_KEY!,
network: process.env.NILLION_NETWORK || 'testnet',
},
// NEAR Cloud AI
near: {
apiKey: process.env.NEAR_API_KEY!,
model: process.env.NEAR_MODEL || 'gpt oss 120b',
endpoint: 'https://cloud.near.ai/v1/verify',
},
// IPFS
ipfs: {
jwt: process.env.PINATA_JWT!,
gateway: process.env.PINATA_GATEWAY || 'https://gateway.pinata.cloud',
spellbookCid: process.env.SPELLBOOK_CID!,
},
// Zcash
zcash: {
network: process.env.ZCASH_NETWORK || 'testnet',
server: process.env.ZCASH_SERVER || 'https://zec.rocks:443',
dataDir: process.env.ZCASH_DATA_DIR!,
},
// Database
database: {
url: process.env.DATABASE_URL!,
},
// Oracle settings
oracle: {
checkInterval: parseInt(process.env.ORACLE_CHECK_INTERVAL || '30') * 1000,
retryAttempts: parseInt(process.env.ORACLE_RETRY_ATTEMPTS || '3'),
retryDelay: parseInt(process.env.ORACLE_RETRY_DELAY || '60') * 1000,
},
// Economic model
economics: {
proverb_cost: 0.01,
public_split: 0.618,
private_split: 0.382,
network_fee: 0.0001,
},
};
// Validate required env vars
const required = [
'NILLION_API_KEY',
'NEAR_API_KEY',
'PINATA_JWT',
'SPELLBOOK_CID',
'ZCASH_DATA_DIR',
'DATABASE_URL',
];
for (const key of required) {
if (!process.env[key]) {
throw new Error(`Missing required environment variable: ${key}`);
}
}
Step 1.3: Create Database Module
// src/database.ts
import { Pool, QueryResult } from 'pg';
import { config } from './config';
const pool = new Pool({
connectionString: config.database.url,
});
export interface Submission {
id: number;
tracking_code: string;
sender_address: string;
proverb_text: string;
amount_zec: number;
txid: string;
status: string;
created_at: Date;
}
export interface Verification {
id: number;
submission_id: number;
ai_provider: string;
quality_score: number;
matched_act: string;
reasoning: string;
verified_at: Date;
}
export interface Inscription {
id: number;
submission_id: number;
public_txid: string;
private_txid: string;
public_amount: number;
private_amount: number;
inscribed_at: Date;
}
export const db = {
// Test connection
async testConnection(): Promise<boolean> {
try {
await pool.query('SELECT 1');
return true;
} catch (error) {
console.error('Database connection failed:', error);
return false;
}
},
// Create submission
async createSubmission(data: {
tracking_code: string;
sender_address: string;
proverb_text: string;
amount_zec: number;
txid: string;
memo_text?: string;
}): Promise<Submission> {
const result = await pool.query(
`INSERT INTO submissions
(tracking_code, sender_address, proverb_text, amount_zec, txid, memo_text, status)
VALUES ($1, $2, $3, $4, $5, $6, 'pending')
RETURNING *`,
[
data.tracking_code,
data.sender_address,
data.proverb_text,
data.amount_zec,
data.txid,
data.memo_text,
]
);
return result.rows[0];
},
// Get pending submissions
async getPendingSubmissions(): Promise<Submission[]> {
const result = await pool.query(
`SELECT * FROM submissions WHERE status = 'pending' ORDER BY created_at ASC`
);
return result.rows;
},
// Update submission status
async updateSubmissionStatus(id: number, status: string): Promise<void> {
await pool.query(
`UPDATE submissions SET status = $1 WHERE id = $2`,
[status, id]
);
},
// Create verification
async createVerification(data: {
submission_id: number;
ai_provider: string;
quality_score: number;
matched_act: string;
reasoning: string;
}): Promise<Verification> {
const result = await pool.query(
`INSERT INTO verifications
(submission_id, ai_provider, quality_score, matched_act, reasoning)
VALUES ($1, $2, $3, $4, $5)
RETURNING *`,
[
data.submission_id,
data.ai_provider,
data.quality_score,
data.matched_act,
data.reasoning,
]
);
return result.rows[0];
},
// Create inscription
async createInscription(data: {
submission_id: number;
public_txid: string;
private_txid: string;
public_amount: number;
private_amount: number;
network_fee: number;
}): Promise<Inscription> {
const result = await pool.query(
`INSERT INTO inscriptions
(submission_id, public_txid, private_txid, public_amount, private_amount, network_fee)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING *`,
[
data.submission_id,
data.public_txid,
data.private_txid,
data.public_amount,
data.private_amount,
data.network_fee,
]
);
return result.rows[0];
},
// Get submission by tracking code
async getSubmissionByTrackingCode(code: string): Promise<Submission | null> {
const result = await pool.query(
`SELECT * FROM submissions WHERE tracking_code = $1`,
[code]
);
return result.rows[0] || null;
},
// Get verification for submission
async getVerification(submissionId: number): Promise<Verification | null> {
const result = await pool.query(
`SELECT * FROM verifications WHERE submission_id = $1`,
[submissionId]
);
return result.rows[0] || null;
},
// Get stats
async getStats(): Promise<any> {
const result = await pool.query(`SELECT * FROM proverb_stats`);
return result.rows[0];
},
// Close pool
async close(): Promise<void> {
await pool.end();
},
};
Step 1.4: Create Zcash Client Module
// src/zcash-client.ts
import { exec } from 'child_process';
import { promisify } from 'util';
import { config } from './config';
const execAsync = promisify(exec);
export interface ZcashTransaction {
txid: string;
amount: number;
address: string;
memo?: string;
confirmations: number;
blockHeight?: number;
}
export class ZcashClient {
private cliPath = 'zecwallet-cli';
// Execute zecwallet-cli command
private async execute(command: string): Promise<string> {
const fullCommand = `${this.cliPath} --server ${config.zcash.server} --data-dir ${config.zcash.dataDir} --command "${command}"`;
try {
const { stdout, stderr } = await execAsync(fullCommand);
if (stderr) {
console.error('Zcash CLI error:', stderr);
}
return stdout;
} catch (error: any) {
throw new Error(`Zcash CLI failed: ${error.message}`);
}
}
// Get list of transactions
async listTransactions(): Promise<ZcashTransaction[]> {
const output = await this.execute('list');
// Parse output (format depends on zecwallet-cli version)
return this.parseTransactionList(output);
}
// Get new transactions since last check
async getNewTransactions(lastHeight: number): Promise<ZcashTransaction[]> {
const allTxs = await this.listTransactions();
return allTxs.filter(tx =>
tx.blockHeight && tx.blockHeight > lastHeight && tx.confirmations > 0
);
}
// Send transaction
async sendTransaction(
to: string,
amount: number,
memo?: string
): Promise<string> {
const memoArg = memo ? `--memo "${memo}"` : '';
const command = `send ${to} ${amount} ${memoArg}`;
const output = await this.execute(command);
return this.parseTxid(output);
}
// Get balance
async getBalance(): Promise<{ transparent: number; shielded: number }> {
const output = await this.execute('balance');
return this.parseBalance(output);
}
// Sync blockchain
async sync(): Promise<void> {
await this.execute('sync');
}
// Get current block height
async getBlockHeight(): Promise<number> {
const output = await this.execute('height');
return parseInt(output.trim());
}
// Parse transaction list (customize based on actual output)
private parseTransactionList(output: string): ZcashTransaction[] {
// Implementation depends on zecwallet-cli output format
// This is a placeholder
const transactions: ZcashTransaction[] = [];
const lines = output.split('\n').filter(l => l.trim());
for (const line of lines) {
// Parse each line based on actual format
// Example format: "txid | amount | address | memo | confirmations"
transactions.push({
txid: '', // parse from line
amount: 0, // parse from line
address: '', // parse from line
memo: undefined, // parse from line
confirmations: 0, // parse from line
});
}
return transactions;
}
// Parse balance output
private parseBalance(output: string): { transparent: number; shielded: number } {
// Parse balance from output
return {
transparent: 0, // parse from output
shielded: 0, // parse from output
};
}
// Parse transaction ID from send output
private parseTxid(output: string): string {
// Extract txid from output
const match = output.match(/[a-f0-9]{64}/);
return match ? match[0] : '';
}
}
export const zcashClient = new ZcashClient();
Step 1.5: Test Foundation
// src/test-foundation.ts
import { db } from './database';
import { zcashClient } from './zcash-client';
import { config } from './config';
async function testFoundation() {
console.log('Testing Foundation...\n');
// Test 1: Configuration
console.log('1. Configuration:');
console.log(' Nillion API Key:', config.nillion.apiKey.substring(0, 10) + '...');
console.log(' NEAR Cloud AI API Key:', config.near.apiKey.substring(0, 10) + '...');
console.log(' Spellbook CID:', config.ipfs.spellbookCid);
console.log(' โ Configuration loaded\n');
// Test 2: Database
console.log('2. Database:');
const dbConnected = await db.testConnection();
console.log(' Connection:', dbConnected ? 'โ Connected' : 'โ Failed');
const stats = await db.getStats();
console.log(' Stats:', stats);
console.log(' โ Database operational\n');
// Test 3: Zcash
console.log('3. Zcash:');
try {
const balance = await zcashClient.getBalance();
console.log(' Balance:', balance);
const height = await zcashClient.getBlockHeight();
console.log(' Height:', height);
console.log(' โ Zcash client working\n');
} catch (error) {
console.log(' โ Zcash client error:', error);
}
// Test 4: Create test submission
console.log('4. Test Submission:');
try {
const submission = await db.createSubmission({
tracking_code: 'TEST-' + Date.now(),
sender_address: 't1test...',
proverb_text: 'Privacy requires separation, not mere intention.',
amount_zec: 0.01,
txid: 'test_' + Date.now(),
});
console.log(' Created:', submission.id);
console.log(' โ Submission created\n');
} catch (error) {
console.log(' โ Submission error:', error);
}
console.log('Foundation tests complete!');
await db.close();
}
testFoundation().catch(console.error);
Run test:
npm run test
Expected Output:
Testing Foundation...
1. Configuration:
Nillion API Key: nillion_xxxxxxxxxx...
NEAR Cloud AI API Key: xxxxxxxxxx...
Spellbook CID: QmXxxx...
โ Configuration loaded
2. Database:
Connection: โ Connected
Stats: { total_submissions: 0, completed: 0, pending: 0 }
โ Database operational
3. Zcash:
Balance: { transparent: 0.5, shielded: 0 }
Height: 2345678
โ Zcash client working
4. Test Submission:
Created: 1
โ Submission created
Foundation tests complete!
Phase 2: Backend (Week 2)
Goals
- IPFS integration working
- AI verification functional
- Nillion TEE deployed
- Transaction signing operational
Step 2.1: Create IPFS Client
// src/ipfs-client.ts
import axios from 'axios';
import { config } from './config';
export interface SpellbookAct {
id: string;
title: string;
description: string;
category: string;
keywords: string[];
}
export interface Spellbook {
version: string;
created_at: string;
description: string;
acts: SpellbookAct[];
}
export class IPFSClient {
private gateway = config.ipfs.gateway;
private spellbookCid = config.ipfs.spellbookCid;
private cache: Spellbook | null = null;
// Fetch spellbook from IPFS
async fetchSpellbook(): Promise<Spellbook> {
if (this.cache) {
return this.cache;
}
const url = `${this.gateway}/ipfs/${this.spellbookCid}`;
try {
const response = await axios.get(url, {
timeout: 10000,
headers: {
'Accept': 'application/json',
},
});
this.cache = response.data;
return this.cache;
} catch (error: any) {
throw new Error(`Failed to fetch spellbook: ${error.message}`);
}
}
// Get specific act by ID
async getAct(actId: string): Promise<SpellbookAct | null> {
const spellbook = await this.fetchSpellbook();
return spellbook.acts.find(act => act.id === actId) || null;
}
// Search acts by keyword
async searchActs(keyword: string): Promise<SpellbookAct[]> {
const spellbook = await this.fetchSpellbook();
const lowerKeyword = keyword.toLowerCase();
return spellbook.acts.filter(act =>
act.keywords.some(k => k.toLowerCase().includes(lowerKeyword)) ||
act.title.toLowerCase().includes(lowerKeyword) ||
act.description.toLowerCase().includes(lowerKeyword)
);
}
// Clear cache
clearCache(): void {
this.cache = null;
}
}
export const ipfsClient = new IPFSClient();
Step 2.2: Create AI Verifier
// src/near-verifier.ts
import axios from 'axios';
import { config } from './config';
import { Spellbook } from './ipfs-client';
export interface VerificationResult {
quality_score: number;
matched_act: string;
reasoning: string;
approved: boolean;
}
export class NearVerifier {
private endpoint = config.near.endpoint;
private apiKey = config.near.apiKey;
private model = config.near.model;
// Verify proverb
async verify(proverb: string, spellbook: Spellbook): Promise<VerificationResult> {
try {
const response = await axios.post(
this.endpoint,
{
model: this.model,
proverb: proverb,
spellbook_acts: spellbook.acts,
context: 'privacy_protocol',
},
{
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
timeout: 30000,
}
);
return response.data;
} catch (error: any) {
console.error('NEAR Cloud AI verification failed:', error.message);
// Fallback to basic pattern matching
return this.fallbackVerification(proverb, spellbook);
}
}
// Fallback verification (basic pattern matching)
private fallbackVerification(proverb: string, spellbook: Spellbook): VerificationResult {
const lowerProverb = proverb.toLowerCase();
// Check for key privacy concepts
const concepts = [
'privacy', 'separation', 'architecture', 'delegation',
'boundary', 'sovereignty', 'protection', 'encryption',
];
const matchedConcepts = concepts.filter(concept =>
lowerProverb.includes(concept)
);
const quality_score = Math.min(matchedConcepts.length / concepts.length, 0.7);
// Try to match an act
let matched_act = 'act-01-shield'; // default
for (const act of spellbook.acts) {
for (const keyword of act.keywords) {
if (lowerProverb.includes(keyword.toLowerCase())) {
matched_act = act.id;
break;
}
}
}
return {
quality_score,
matched_act,
reasoning: 'Fallback verification (AI unavailable). Basic pattern matching used.',
approved: quality_score >= 0.5,
};
}
}
export const nearVerifier = new NearVerifier();
Step 2.3: Create Nillion Signer
// src/nillion-signer.ts
import { NillionClient, SecretSigner } from '@nillion/client-web';
import { config } from './config';
export class NillionSigner {
private client: NillionClient;
private keyStoreId: string | null = null;
constructor() {
this.client = new NillionClient({
network: config.nillion.network,
apiKey: config.nillion.apiKey,
});
}
// Initialize and store Zcash spending key
async initializeKey(privateKey: Buffer): Promise<string> {
try {
this.keyStoreId = await SecretSigner.storeKey({
client: this.client,
privateKey: privateKey,
algorithm: 'ECDSA',
});
console.log('Zcash key stored in SecretSigner:', this.keyStoreId);
return this.keyStoreId;
} catch (error: any) {
throw new Error(`Failed to store key: ${error.message}`);
}
}
// Sign transaction
async signTransaction(txHash: Buffer): Promise<Buffer> {
if (!this.keyStoreId) {
throw new Error('Key not initialized. Call initializeKey() first.');
}
try {
const signature = await SecretSigner.sign({
client: this.client,
storeId: this.keyStoreId,
message: txHash,
algorithm: 'ECDSA',
});
return signature;
} catch (error: any) {
throw new Error(`Failed to sign transaction: ${error.message}`);
}
}
// Get attestation
async getAttestation(): Promise<string> {
try {
const attestation = await this.client.getAttestation();
return attestation;
} catch (error: any) {
throw new Error(`Failed to get attestation: ${error.message}`);
}
}
// Verify attestation
async verifyAttestation(attestation: string): Promise<boolean> {
try {
const valid = await this.client.verifyAttestation(attestation);
return valid;
} catch (error: any) {
console.error('Attestation verification failed:', error.message);
return false;
}
}
}
export const nillionSigner = new NillionSigner();
Step 2.4: Create Main Oracle Loop
// src/index.ts
import { config } from './config';
import { db } from './database';
import { zcashClient } from './zcash-client';
import { ipfsClient } from './ipfs-client';
import { nearVerifier } from './near-verifier';
import { nillionSigner } from './nillion-signer';
let lastProcessedHeight = 0;
let isProcessing = false;
async function processSubmissions() {
if (isProcessing) {
console.log('Already processing, skipping...');
return;
}
isProcessing = true;
try {
// Get current height
const currentHeight = await zcashClient.getBlockHeight();
// Get new transactions
const newTxs = await zcashClient.getNewTransactions(lastProcessedHeight);
console.log(`Found ${newTxs.length} new transactions`);
for (const tx of newTxs) {
await processTransaction(tx);
}
lastProcessedHeight = currentHeight;
} catch (error) {
console.error('Error processing submissions:', error);
} finally {
isProcessing = false;
}
}
async function processTransaction(tx: any) {
try {
console.log(`Processing transaction: ${tx.txid}`);
// Extract tracking code and proverb from memo
const { tracking_code, proverb } = parseMemo(tx.memo);
// Check if already processed
const existing = await db.getSubmissionByTrackingCode(tracking_code);
if (existing) {
console.log(`Already processed: ${tracking_code}`);
return;
}
// Create submission
const submission = await db.createSubmission({
tracking_code,
sender_address: tx.address,
proverb_text: proverb,
amount_zec: tx.amount,
txid: tx.txid,
memo_text: tx.memo,
});
console.log(`Created submission: ${submission.id}`);
// Fetch spellbook
const spellbook = await ipfsClient.fetchSpellbook();
// Verify with AI
const verification = await nearVerifier.verify(proverb, spellbook);
// Save verification
await db.createVerification({
submission_id: submission.id,
ai_provider: 'near',
quality_score: verification.quality_score,
matched_act: verification.matched_act,
reasoning: verification.reasoning,
});
console.log(`Verification complete: ${verification.quality_score}`);
if (verification.approved) {
// Sign and inscribe
await inscribeProverb(submission, proverb);
} else {
await db.updateSubmissionStatus(submission.id, 'rejected');
console.log(`Submission rejected: quality too low`);
}
} catch (error) {
console.error(`Error processing transaction:`, error);
}
}
async function inscribeProverb(submission: any, proverb: string) {
try {
const publicAmount = config.economics.proverb_cost * config.economics.public_split;
const privateAmount = config.economics.proverb_cost * config.economics.private_split;
// Create inscription transaction (public - to spellbook transparent address)
const publicTxid = await zcashClient.sendTransaction(
config.zcash.publicInscriptionAddress, // Spellbook's transparent address
publicAmount,
`PROVERB:${submission.tracking_code}:${proverb}` // Proverb + proof of revelation
);
// Create shielded transaction (private)
const privateTxid = await zcashClient.sendTransaction(
'SHIELDED_ADDRESS', // Replace with actual shielded address
privateAmount
);
// Save inscription
await db.createInscription({
submission_id: submission.id,
public_txid: publicTxid,
private_txid: privateTxid,
public_amount: publicAmount,
private_amount: privateAmount,
network_fee: config.economics.network_fee,
});
await db.updateSubmissionStatus(submission.id, 'completed');
console.log(`Inscription complete: ${publicTxid}`);
} catch (error) {
console.error(`Error inscribing:`, error);
await db.updateSubmissionStatus(submission.id, 'failed');
}
}
function parseMemo(memo: string): { tracking_code: string; proverb: string } {
// Parse memo format: "TRACK:CODE|proverb text here"
const parts = memo.split('|');
const tracking_code = parts[0].replace('TRACK:', '');
const proverb = parts[1] || '';
return { tracking_code, proverb };
}
// Main loop
async function main() {
console.log('Oracle Swordsman starting...');
// Test connections
const dbOk = await db.testConnection();
if (!dbOk) {
console.error('Database connection failed!');
process.exit(1);
}
console.log('Database connected โ');
// Get initial height
lastProcessedHeight = await zcashClient.getBlockHeight();
console.log(`Starting from height: ${lastProcessedHeight}`);
// Start processing loop
setInterval(processSubmissions, config.oracle.checkInterval);
console.log(`Oracle running (check interval: ${config.oracle.checkInterval}ms)`);
}
main().catch(console.error);
Step 2.5: Test Backend
# Start oracle
npm run dev
# In another terminal, send test transaction
# (requires testnet ZEC and wallet setup)
Phase 3: Frontend (Week 3)
Goals
- User submission interface
- Status tracking
- Quality score display
- Mobile responsive
Step 3.1: Initialize Mage Agent
cd ~/proverb-protocol/mage-agent
# Create Next.js app
npx create-next-app@latest . --typescript --tailwind --app --no-src-dir
# Install dependencies
npm install axios qrcode.react
Step 3.2: Create API Route
// app/api/submit/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { proverb, tracking_code } = body;
// Validate
if (!proverb || !tracking_code) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
);
}
// Return payment info
return NextResponse.json({
success: true,
payment_address: process.env.ZCASH_PAYMENT_ADDRESS,
amount: 0.01,
memo: `TRACK:${tracking_code}|${proverb}`,
tracking_code,
});
} catch (error) {
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
// app/api/status/[code]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database'; // You'll need to set this up
export async function GET(
request: NextRequest,
{ params }: { params: { code: string } }
) {
try {
const submission = await db.getSubmissionByTrackingCode(params.code);
if (!submission) {
return NextResponse.json(
{ error: 'Submission not found' },
{ status: 404 }
);
}
const verification = await db.getVerification(submission.id);
return NextResponse.json({
status: submission.status,
quality_score: verification?.quality_score,
matched_act: verification?.matched_act,
reasoning: verification?.reasoning,
txid: submission.txid,
});
} catch (error) {
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
Step 3.3: Create Submission Page
// app/page.tsx
'use client';
import { useState } from 'react';
import axios from 'axios';
import QRCode from 'qrcode.react';
export default function Home() {
const [proverb, setProverb] = useState('');
const [trackingCode, setTrackingCode] = useState('');
const [paymentInfo, setPaymentInfo] = useState<any>(null);
const [status, setStatus] = useState<any>(null);
const handleSubmit = async () => {
const code = 'PROV-' + Date.now();
setTrackingCode(code);
const response = await axios.post('/api/submit', {
proverb,
tracking_code: code,
});
setPaymentInfo(response.data);
};
const checkStatus = async () => {
const response = await axios.get(`/api/status/${trackingCode}`);
setStatus(response.data);
};
return (
<main className="min-h-screen p-8 max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8">Proverb Revelation Protocol</h1>
{!paymentInfo ? (
<div>
<textarea
value={proverb}
onChange={(e) => setProverb(e.target.value)}
placeholder="Enter your proverb here..."
className="w-full p-4 border rounded-lg mb-4"
rows={6}
/>
<button
onClick={handleSubmit}
className="bg-blue-600 text-white px-6 py-2 rounded-lg"
>
Submit Proverb
</button>
</div>
) : (
<div>
<h2 className="text-2xl mb-4">Payment Required</h2>
<p>Send exactly 0.01 ZEC to:</p>
<code className="block bg-gray-100 p-2 my-4">
{paymentInfo.payment_address}
</code>
<p className="mb-2">Include this memo:</p>
<code className="block bg-gray-100 p-2 mb-4 text-xs">
{paymentInfo.memo}
</code>
<QRCode value={`zcash:${paymentInfo.payment_address}?amount=0.01&memo=${paymentInfo.memo}`} />
<div className="mt-8">
<p>Tracking Code: <code>{trackingCode}</code></p>
<button
onClick={checkStatus}
className="mt-4 bg-green-600 text-white px-6 py-2 rounded-lg"
>
Check Status
</button>
</div>
{status && (
<div className="mt-4 p-4 border rounded-lg">
<p>Status: <strong>{status.status}</strong></p>
{status.quality_score && (
<>
<p>Quality Score: <strong>{status.quality_score}</strong></p>
<p>Matched Act: <strong>{status.matched_act}</strong></p>
<p className="mt-2 text-sm">{status.reasoning}</p>
</>
)}
</div>
)}
</div>
)}
</main>
);
}
Phase 4: Production (Week 4)
Step 4.1: Testing Checklist
# Test Oracle
- [ ] Can detect Zcash transactions
- [ ] Parses memo correctly
- [ ] Fetches spellbook from IPFS
- [ ] AI verification works
- [ ] Signs with Nillion
- [ ] Broadcasts transactions
- [ ] Updates database correctly
# Test Frontend
- [ ] Can submit proverb
- [ ] Displays payment info
- [ ] Shows QR code
- [ ] Tracks status
- [ ] Shows quality score
# Integration Test
- [ ] End-to-end: submission โ inscription
- [ ] 61.8/38.2 golden ratio split correct
- [ ] Tracking codes match
- [ ] All data persisted
Step 4.2: Production Deployment
# Build oracle
cd oracle-swordsman
npm run build
# Create systemd service
sudo cat > /etc/systemd/system/oracle-swordsman.service << 'EOF'
[Unit]
Description=Oracle Swordsman
After=network.target
[Service]
Type=simple
User=your_user
WorkingDirectory=/home/your_user/proverb-protocol/oracle-swordsman
ExecStart=/usr/bin/node dist/index.js
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# Start service
sudo systemctl enable oracle-swordsman
sudo systemctl start oracle-swordsman
# Build frontend
cd ../mage-agent
npm run build
# Deploy with Nginx
# Configure reverse proxy for Next.js
SpellWeb Feature
The SpellWeb is an interactive graph visualization showing your spell constellation. It includes several advanced features:
Features
- Constellation Mode - View all spells connected by grimoire, sequence, and custom links
- Draw Mode - Click two nodes to create custom connections between them
- Randomise Layout - Scatter nodes and let the physics simulation reorganize them
- Expand/Fullscreen - View the full graph in fullscreen mode (press Escape to exit)
- Grimoire Filter Panel - Toggle individual grimoires on/off to focus on specific spell groups
Key Files
src/
โโโ components/spellweb/
โ โโโ SpellwebViewer.tsx # Basic viewer (used on /spells)
โ โโโ SpellwebViewerAgentic.tsx # Enhanced viewer with draw/constellation modes
โโโ lib/spellweb/
โ โโโ types.ts # Node, Link, DrawnLink type definitions
โ โโโ builder.ts # Builds graph from selected spells
โ โโโ labels.ts # Grimoire colors, emojis, short labels
โโโ app/web/page.tsx # Web page with grimoire filter panel
SpellwebViewerAgentic Props
interface SpellwebViewerAgenticProps {
data: SpellwebData; // Nodes and links from builder
grimoireFilter?: string[]; // Active grimoire IDs to show
onNodeSelect?: (node) => void;// Callback when node is clicked
fullHeight?: boolean; // Fill container height
}
Link Types
| Type | Description | Visual |
|---|---|---|
grimoire |
Spell โ grimoire hub | Gray solid |
sequence |
Spell โ next spell | Gray dashed |
cluster |
Skill โ skills hub | Gray solid |
constellation |
Cross-grimoire | Orange solid |
drawn |
User-drawn (temporary) | Green dashed |
saved |
User-saved (persistent) | Green dashed |
localStorage Persistence
User-drawn maps are saved to agentprivacy-web-saved-map in localStorage. When you click "Save map" in Draw mode, your custom links persist and appear in Constellation mode.
Next Steps
โ Build Complete!
You now have:
- Working Oracle Swordsman
- Functional Mage Agent
- Complete integration
- Production deployment
- SpellWeb visualization with draw/constellation modes
Next: Reference docs/04-API_REFERENCE.md for detailed code patterns
Then: Use docs/05-ROADMAP.md to track your integration checklist