Setup Oracle server

This commit is contained in:
ngoc 2025-12-10 16:14:42 +07:00
parent 98eeb8b38b
commit 5710f1d83d
No known key found for this signature in database
GPG Key ID: 51FE6110113A5C32
12 changed files with 1181 additions and 2 deletions

View File

@ -1 +1,40 @@
OPENAI_API_KEY=sk-proj...
# Natural Language Agreement Oracle Configuration
# ================================
# DEPLOYMENT CONFIGURATION
# ================================
# Deployer's private key (used for contract deployment)
# IMPORTANT: This should be a funded account with enough ETH for gas
# NEVER commit your real private key!
# The key below is from Anvil's default accounts - DO NOT USE IN PRODUCTION
DEPLOYER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
# ================================
# ORACLE CONFIGURATION
# ================================
# Blockchain RPC URL (required for oracle)
# Examples:
# Local: http://localhost:8545
# Sepolia: https://sepolia.infura.io/v3/YOUR-INFURA-KEY
# Mainnet: https://mainnet.infura.io/v3/YOUR-INFURA-KEY
RPC_URL=http://localhost:8545
# Oracle operator's private key (required for oracle)
# This account submits arbitration decisions on-chain
# IMPORTANT: Ensure this account has sufficient ETH for gas!
# The key below is from Anvil's default accounts - DO NOT USE IN PRODUCTION
ORACLE_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
# EAS (Ethereum Attestation Service) Contract Address (optional)
# If you used the deploy script, get this from deployments/<network>.json
# EAS_CONTRACT_ADDRESS=0x...
# ================================
# AI CONFIGURATION
# ================================
# OpenAI API Key (required for oracle)
# Get your API key from https://platform.openai.com/api-keys
OPENAI_API_KEY=sk-proj...

5
.gitignore vendored
View File

@ -14,6 +14,11 @@ coverage
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
anvil.log
# process ids
*.pid
.anvil.pid
# dotenv environment variable files
.env

75
QUICKSTART.md Normal file
View File

@ -0,0 +1,75 @@
# Quick Start Guide
Get your Natural Language Agreement Oracle running in **under 2 minutes**!
## Prerequisites
- [Bun](https://bun.sh) installed
- [Foundry](https://book.getfoundry.sh/getting-started/installation) installed
- Both `alkahest` and `natural-language-agreements` repos cloned in same parent directory
- OpenAI API key
## One-Command Setup
```bash
export OPENAI_API_KEY=sk-your-key-here
./scripts/dev.sh
```
Done! The oracle is now running and listening for arbitration requests.
**To stop:**
```bash
./scripts/stop.sh
```
## What Just Happened?
The script:
1. ✅ Checked prerequisites
2. ✅ Started Anvil (local blockchain)
3. ✅ Deployed all contracts
4. ✅ Started the oracle
## Test It
In another terminal:
```bash
bun test tests/nlaOracle.test.ts
```
Watch the oracle terminal to see it process the arbitration!
## Manual Steps (Optional)
If you want more control:
```bash
# Terminal 1: Blockchain
anvil
# Terminal 2: Deploy and start oracle
bun run deploy
bun run oracle
# Terminal 3: Test
bun test tests/nlaOracle.test.ts
```
## Deploy to Testnet
```bash
# Get Sepolia ETH from faucet first
export DEPLOYER_PRIVATE_KEY=0x...
bun run setups/deploy.ts --network sepolia --rpc-url https://sepolia.infura.io/v3/YOUR-KEY
# Start oracle
export ORACLE_PRIVATE_KEY=0x...
export OPENAI_API_KEY=sk-...
bun run setups/oracle.ts sepolia
```
## Need Help?
- Full docs: [README.md](README.md)
- Example test: `tests/nlaOracle.test.ts`

232
README.md
View File

@ -41,8 +41,188 @@ cd natural-language-agreements
bun install
```
## Quick Start
### Option 1: Automated Setup (Easiest - 1 command!)
Set your OpenAI API key and run everything:
```bash
export OPENAI_API_KEY=sk-your-key-here
./scripts/dev.sh
```
This will:
- ✅ Check all prerequisites
- ✅ Start Anvil (local blockchain)
- ✅ Deploy all contracts
- ✅ Start the oracle
- ✅ Ready to test!
To stop everything:
```bash
./scripts/stop.sh
```
### Option 2: Manual Setup (Step by Step)
#### 1. Start Local Blockchain
```bash
# Terminal 1: Start Anvil
anvil
```
#### 2. Deploy Contracts
```bash
# Terminal 2: Deploy to localhost
export OPENAI_API_KEY=sk-your-key-here
./scripts/deploy.sh localhost
```
This creates `deployments/localhost.json` with all contract addresses.
#### 3. Start Oracle
```bash
# Terminal 2 (or 3): Start oracle
./scripts/start-oracle.sh localhost
```
#### 4. Test It
```bash
# Terminal 3 (or 4): Run tests
bun test tests/nlaOracle.test.ts
```
Watch the oracle terminal - you'll see it process arbitration requests in real-time!
## Deployment to Other Networks
### Sepolia Testnet
```bash
# 1. Get Sepolia ETH from faucet
# 2. Set your keys
export DEPLOYER_PRIVATE_KEY=0x...
export ORACLE_PRIVATE_KEY=0x...
export OPENAI_API_KEY=sk-...
# 3. Deploy
./scripts/deploy.sh sepolia https://sepolia.infura.io/v3/YOUR-KEY
# 4. Start oracle
./scripts/start-oracle.sh sepolia
```
### Mainnet
```bash
# ⚠️ PRODUCTION - Be careful!
export DEPLOYER_PRIVATE_KEY=0x...
export ORACLE_PRIVATE_KEY=0x...
export OPENAI_API_KEY=sk-...
# Deploy
./scripts/deploy.sh mainnet https://mainnet.infura.io/v3/YOUR-KEY
# Start oracle (consider running as a service)
./scripts/start-oracle.sh mainnet
```
## Available Scripts
```bash
./scripts/dev.sh # Complete local setup (all-in-one)
./scripts/deploy.sh [network] # Deploy contracts to network
./scripts/start-oracle.sh [network] # Start oracle for network
./scripts/stop.sh # Stop all services
```
## Production Deployment
For production, run the oracle as a background service:
### Using systemd (Linux)
```bash
# Copy service file
sudo cp deployment/nla-oracle.service /etc/systemd/system/
# Edit the service file with your paths and config
sudo nano /etc/systemd/system/nla-oracle.service
# Enable and start
sudo systemctl enable nla-oracle
sudo systemctl start nla-oracle
# View logs
sudo journalctl -u nla-oracle -f
```
### Using nohup (Simple)
```bash
# Start in background
nohup ./scripts/start-oracle.sh mainnet > oracle.log 2>&1 &
# Save PID
echo $! > oracle.pid
# Stop later
kill $(cat oracle.pid)
```
### Using screen (Simple)
```bash
# Start screen session
screen -S oracle
# Run oracle
./scripts/start-oracle.sh mainnet
# Detach: Ctrl+A, then D
# Reattach: screen -r oracle
```
## Monitoring
### View Oracle Logs
```bash
# If using systemd
sudo journalctl -u nla-oracle -f
# If using nohup
tail -f oracle.log
# If using Anvil
tail -f anvil.log
```
### Check Oracle Status
```bash
# Check if oracle is running
ps aux | grep "bun run oracle"
# Check if Anvil is running
lsof -i :8545
```
## Usage
### Running Tests
```bash
bun test
```
### Development Mode
To run:
```bash
@ -52,3 +232,55 @@ bun run index.ts
## Development
This project was created using `bun init` in bun v1.2.20. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
## Project Structure
```
natural-language-agreements/
├── oracle.ts # Oracle CLI application
├── deploy.ts # Contract deployment script
├── index.ts # Development entry point
├── clients/
│ └── nla.ts # Natural Language Agreement client
├── tests/
│ ├── nla.test.ts # Basic tests
│ └── nlaOracle.test.ts # Oracle arbitration tests
├── deployments/ # Deployment addresses (generated)
│ ├── localhost.json
│ ├── sepolia.json
│ └── mainnet.json
├── package.json
└── README.md
```
## Troubleshooting
### "Cannot find module 'alkahest-ts'"
- Ensure alkahest is cloned in the parent directory
- Run `bun install` in both alkahest and this project
### "Deployer has no ETH"
- Fund your deployer account before running deployment
- For testnets, use a faucet
### "Oracle not detecting arbitration requests"
- Verify RPC URL is correct and accessible
- Check that EAS contract address matches deployment
- Ensure oracle has ETH for gas
- Check polling interval (try lowering it)
### "OpenAI API errors"
- Verify API key is valid and active
- Check OpenAI usage limits and billing
- Ensure model name is correct (e.g., "gpt-4o")
## Security Notes
⚠️ **Important Security Considerations:**
- Never commit your real private keys to version control
- Use environment variables or secure secret management for production
- The `.env` file is gitignored by default
- The example private key in `.env.example` is from Anvil and should NEVER be used in production
- Ensure your OpenAI API key is kept secure and not exposed in logs or error messages
- Run the oracle in a secure environment with proper access controls

View File

@ -0,0 +1,15 @@
{
"network": "localhost",
"chainId": 31337,
"rpcUrl": "http://localhost:8545",
"deployedAt": "2025-12-10T09:13:15.595Z",
"deployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"addresses": {
"easSchemaRegistry": "0x5fbdb2315678afecb367f032d93f642f64180aa3",
"eas": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512",
"trustedOracleArbiter": "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0",
"stringObligation": "0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9",
"erc20EscrowObligation": "0xdc64a140aa3e981100a9beca4e685f962f0cf6c9",
"erc20PaymentObligation": "0x5fc8d32690cc91d4c39d9d3abcbd16989f875707"
}
}

View File

@ -9,7 +9,11 @@
"scripts": {
"dev": "bun run index.ts",
"start": "bun run index.ts",
"test": "bun test ./tests --exclude alkahest-ts/** "
"test": "bun test ./tests --exclude alkahest-ts/** ",
"setup": "./scripts/dev.sh",
"deploy": "./scripts/deploy.sh",
"oracle": "./scripts/start-oracle.sh",
"stop": "./scripts/stop.sh"
},
"peerDependencies": {
"typescript": "^5.9.3"

64
scripts/deploy.sh Executable file
View File

@ -0,0 +1,64 @@
#!/bin/bash
# Deploy Alkahest contracts to blockchain
# Usage: ./deploy.sh [network] [rpc-url]
set -e # Exit on error
# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Default values
NETWORK=${1:-localhost}
RPC_URL=${2:-http://localhost:8545}
echo -e "${BLUE}🚀 Deploying Alkahest Contracts${NC}\n"
# Check if deployer private key is set
if [ -z "$DEPLOYER_PRIVATE_KEY" ]; then
echo -e "${YELLOW}⚠️ DEPLOYER_PRIVATE_KEY not set, using default Anvil key${NC}"
export DEPLOYER_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
fi
# Check if alkahest exists
if [ ! -d "../alkahest" ]; then
echo -e "${RED}❌ Error: alkahest repository not found in parent directory${NC}"
echo "Please clone it first: git clone https://github.com/arkhai-io/alkahest.git"
exit 1
fi
# Check if contract artifacts exist
if [ ! -d "../alkahest/sdks/ts/src/contracts" ]; then
echo -e "${RED}❌ Error: Contract artifacts not found${NC}"
echo "Expected path: ../alkahest/sdks/ts/src/contracts"
exit 1
fi
echo -e "${GREEN}✅ Contract artifacts found${NC}\n"
# Run deployment
echo -e "${BLUE}📝 Deploying to ${NETWORK}...${NC}"
bun run setups/deploy.ts --network "$NETWORK" --rpc-url "$RPC_URL"
# Check if deployment was successful
if [ $? -eq 0 ]; then
echo -e "\n${GREEN}✨ Deployment complete!${NC}"
echo -e "${BLUE}Deployment file saved to: deployments/${NETWORK}.json${NC}\n"
echo -e "${YELLOW}Next steps:${NC}"
echo "1. Start the oracle with:"
echo " ./scripts/start-oracle.sh $NETWORK"
echo ""
echo "2. Or using npm script:"
echo " bun run oracle"
echo ""
echo "3. Or manually:"
echo " bun run setups/oracle.ts --deployment ./deployments/${NETWORK}.json"
else
echo -e "\n${RED}❌ Deployment failed${NC}"
exit 1
fi

125
scripts/dev.sh Executable file
View File

@ -0,0 +1,125 @@
#!/bin/bash
# Complete setup and deployment for local development
# This script will:
# 1. Check prerequisites
# 2. Start Anvil
# 3. Deploy contracts
# 4. Start the oracle
set -e
# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
echo -e "${BLUE} Natural Language Agreement Oracle - Quick Setup${NC}"
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}\n"
# Check prerequisites
echo -e "${BLUE}📋 Checking prerequisites...${NC}\n"
# Load .env file if it exists
if [ -f ".env" ]; then
echo -e "${BLUE}📄 Loading .env file...${NC}"
export $(cat .env | grep -v '^#' | xargs)
echo -e "${GREEN}✅ Environment variables loaded${NC}"
fi
# Check Bun
if ! command -v bun &> /dev/null; then
echo -e "${RED}❌ Bun is not installed${NC}"
echo "Please install it: https://bun.sh"
exit 1
fi
echo -e "${GREEN}✅ Bun installed${NC}"
# Check Foundry
if ! command -v forge &> /dev/null; then
echo -e "${RED}❌ Foundry (forge) is not installed${NC}"
echo "Please install it: https://book.getfoundry.sh/getting-started/installation"
exit 1
fi
echo -e "${GREEN}✅ Foundry installed${NC}"
# Check Anvil
if ! command -v anvil &> /dev/null; then
echo -e "${RED}❌ Anvil is not installed${NC}"
echo "Please install Foundry: https://book.getfoundry.sh/getting-started/installation"
exit 1
fi
echo -e "${GREEN}✅ Anvil installed${NC}"
# Check alkahest
if [ ! -d "../alkahest" ]; then
echo -e "${RED}❌ alkahest repository not found${NC}"
echo "Please clone it in the parent directory:"
echo " cd .. && git clone https://github.com/arkhai-io/alkahest.git"
exit 1
fi
echo -e "${GREEN}✅ alkahest repository found${NC}"
# Check OpenAI API key
if [ -z "$OPENAI_API_KEY" ]; then
echo -e "${RED}❌ OPENAI_API_KEY not set${NC}"
echo "Please create a .env file with: OPENAI_API_KEY=sk-your-key-here"
echo "Or export it: export OPENAI_API_KEY=sk-your-key-here"
exit 1
fi
echo -e "${GREEN}✅ OpenAI API key configured${NC}\n"
# Install dependencies
echo -e "${BLUE}📦 Installing dependencies...${NC}\n"
bun install
# Check if Anvil is already running
if lsof -Pi :8545 -sTCP:LISTEN -t >/dev/null ; then
echo -e "${YELLOW}⚠️ Anvil is already running on port 8545${NC}"
read -p "Do you want to kill it and start fresh? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
pkill -f anvil || true
sleep 2
else
echo -e "${BLUE}Using existing Anvil instance${NC}\n"
fi
fi
# Start Anvil in background if not running
if ! lsof -Pi :8545 -sTCP:LISTEN -t >/dev/null ; then
echo -e "${BLUE}🔨 Starting Anvil...${NC}"
anvil > anvil.log 2>&1 &
ANVIL_PID=$!
echo $ANVIL_PID > .anvil.pid
echo -e "${GREEN}✅ Anvil started (PID: $ANVIL_PID)${NC}"
echo " Logs: tail -f anvil.log"
sleep 3
fi
# Deploy contracts
echo -e "\n${BLUE}📝 Deploying contracts...${NC}\n"
export DEPLOYER_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
./scripts/deploy.sh localhost http://localhost:8545
# Start oracle
echo -e "\n${BLUE}🚀 Starting oracle...${NC}\n"
export ORACLE_PRIVATE_KEY="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
./scripts/start-oracle.sh localhost
# Cleanup function
cleanup() {
echo -e "\n${YELLOW}🛑 Shutting down...${NC}"
if [ -f .anvil.pid ]; then
ANVIL_PID=$(cat .anvil.pid)
kill $ANVIL_PID 2>/dev/null || true
rm .anvil.pid
echo -e "${GREEN}✅ Anvil stopped${NC}"
fi
exit 0
}
trap cleanup SIGINT SIGTERM

60
scripts/start-oracle.sh Executable file
View File

@ -0,0 +1,60 @@
#!/bin/bash
# Start the Natural Language Agreement Oracle
# Usage: ./start-oracle.sh [network]
set -e # Exit on error
# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Load .env file if it exists
if [ -f ".env" ]; then
export $(cat .env | grep -v '^#' | xargs)
fi
# Default network
NETWORK=${1:-localhost}
DEPLOYMENT_FILE="./deployments/${NETWORK}.json"
echo -e "${BLUE}🚀 Starting Natural Language Agreement Oracle${NC}\n"
# Check if deployment file exists
if [ ! -f "$DEPLOYMENT_FILE" ]; then
echo -e "${RED}❌ Error: Deployment file not found: $DEPLOYMENT_FILE${NC}"
echo "Please deploy contracts first:"
echo " ./scripts/deploy.sh $NETWORK"
exit 1
fi
# Check if OpenAI API key is set
if [ -z "$OPENAI_API_KEY" ]; then
echo -e "${RED}❌ Error: OPENAI_API_KEY environment variable is not set${NC}"
echo "Please set it:"
echo " export OPENAI_API_KEY=sk-your-key-here"
exit 1
fi
# Check if Oracle private key is set
if [ -z "$ORACLE_PRIVATE_KEY" ]; then
echo -e "${YELLOW}⚠️ ORACLE_PRIVATE_KEY not set, using default Anvil key${NC}"
export ORACLE_PRIVATE_KEY="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
fi
# Display configuration
echo -e "${GREEN}Configuration:${NC}"
echo " Network: $NETWORK"
echo " Deployment: $DEPLOYMENT_FILE"
echo " Oracle Key: ${ORACLE_PRIVATE_KEY:0:6}...${ORACLE_PRIVATE_KEY: -4}"
echo ""
# Start oracle
echo -e "${BLUE}👂 Starting oracle (Press Ctrl+C to stop)...${NC}\n"
bun run setups/oracle.ts \
--deployment "$DEPLOYMENT_FILE" \
--openai-api-key "$OPENAI_API_KEY" \
--private-key "$ORACLE_PRIVATE_KEY"

30
scripts/stop.sh Executable file
View File

@ -0,0 +1,30 @@
#!/bin/bash
# Stop all running oracle and Anvil processes
# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${BLUE}🛑 Stopping Natural Language Agreement services...${NC}\n"
# Stop Anvil
if [ -f .anvil.pid ]; then
ANVIL_PID=$(cat .anvil.pid)
if kill -0 $ANVIL_PID 2>/dev/null; then
kill $ANVIL_PID
echo -e "${GREEN}✅ Stopped Anvil (PID: $ANVIL_PID)${NC}"
fi
rm .anvil.pid
else
# Try to kill any running anvil
pkill -f anvil && echo -e "${GREEN}✅ Stopped Anvil${NC}" || echo -e "${YELLOW}⚠️ No Anvil process found${NC}"
fi
# Stop oracle
pkill -f "bun run oracle" && echo -e "${GREEN}✅ Stopped oracle${NC}" || echo -e "${YELLOW}⚠️ No oracle process found${NC}"
echo -e "\n${GREEN}✨ Cleanup complete${NC}"

287
setups/deploy.ts Normal file
View File

@ -0,0 +1,287 @@
#!/usr/bin/env bun
/**
* Deployment script for Alkahest Natural Language Agreement Oracle
*
* This script deploys all necessary contracts to a blockchain network
* and saves the deployment addresses for use by the oracle CLI.
*/
import { parseArgs } from "util";
import { createWalletClient, http, publicActions, parseEther } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet, sepolia, foundry } from "viem/chains";
import { writeFileSync, existsSync, mkdirSync } from "fs";
import { resolve } from "path";
// Helper function to display usage
function displayHelp() {
console.log(`
Alkahest Contract Deployment CLI
Usage:
bun deploy.ts [options]
Options:
--network <name> Network to deploy to: mainnet, sepolia, localhost (required)
--rpc-url <url> Custom RPC URL (overrides network default)
--private-key <key> Deployer's private key (required)
--output <path> Output file for deployment addresses (default: ./deployments/<network>.json)
--help, -h Display this help message
Environment Variables (alternative to CLI options):
DEPLOYER_PRIVATE_KEY Deployer's private key
RPC_URL Custom RPC URL
Networks:
mainnet Ethereum Mainnet
sepolia Ethereum Sepolia Testnet
localhost Local development (Anvil/Hardhat)
Examples:
# Deploy to local Anvil
bun deploy.ts --network localhost --private-key 0x...
# Deploy to Sepolia
bun deploy.ts --network sepolia --private-key 0x... --rpc-url https://sepolia.infura.io/v3/YOUR-KEY
# Using environment variables
export DEPLOYER_PRIVATE_KEY=0x...
bun deploy.ts --network localhost
`);
}
// Parse command line arguments
function parseCliArgs() {
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
"network": { type: "string" },
"rpc-url": { type: "string" },
"private-key": { type: "string" },
"output": { type: "string" },
"help": { type: "boolean", short: "h" },
},
strict: true,
});
return values;
}
// Get chain configuration
function getChain(network: string) {
switch (network.toLowerCase()) {
case "mainnet":
return mainnet;
case "sepolia":
return sepolia;
case "localhost":
case "local":
return foundry;
default:
throw new Error(`Unknown network: ${network}. Use mainnet, sepolia, or localhost`);
}
}
async function main() {
try {
const args = parseCliArgs();
// Display help if requested
if (args.help) {
displayHelp();
process.exit(0);
}
// Get configuration
const network = args.network;
const privateKey = args["private-key"] || process.env.DEPLOYER_PRIVATE_KEY;
let rpcUrl = args["rpc-url"] || process.env.RPC_URL;
// Validate required parameters
if (!network) {
console.error("❌ Error: Network is required. Use --network <name>");
console.error("Run with --help for usage information.");
process.exit(1);
}
if (!privateKey) {
console.error("❌ Error: Private key is required. Use --private-key or set DEPLOYER_PRIVATE_KEY");
console.error("Run with --help for usage information.");
process.exit(1);
}
// Get chain config
const chain = getChain(network);
// Set default RPC URL if not provided
if (!rpcUrl) {
if (network === "localhost") {
rpcUrl = "http://localhost:8545";
} else {
console.error("❌ Error: RPC URL is required for non-localhost networks. Use --rpc-url");
process.exit(1);
}
}
console.log("🚀 Starting Alkahest Contract Deployment\n");
console.log("Configuration:");
console.log(` 🌐 Network: ${network}`);
console.log(` 📡 RPC URL: ${rpcUrl}`);
console.log(` 🔑 Deployer: ${privateKey.slice(0, 6)}...${privateKey.slice(-4)}\n`);
// Create deployer account and client
const account = privateKeyToAccount(privateKey as `0x${string}`);
const client = createWalletClient({
account,
chain,
transport: http(rpcUrl),
}).extend(publicActions);
console.log(`✅ Deployer address: ${account.address}\n`);
// Check balance
const balance = await client.getBalance({ address: account.address });
console.log(`💰 Deployer balance: ${parseFloat((balance / 10n ** 18n).toString()).toFixed(4)} ETH\n`);
if (balance === 0n) {
console.error("❌ Error: Deployer has no ETH. Please fund the account first.");
process.exit(1);
}
// Import contract artifacts from alkahest
console.log("📦 Loading contract artifacts...\n");
// This requires alkahest to be properly set up
const alkahestPath = "../../alkahest/sdks/ts";
const contractsPath = `${alkahestPath}/src/contracts`;
// Import necessary artifacts
const EAS = await import(`${alkahestPath}/tests/fixtures/EAS.json`);
const SchemaRegistry = await import(`${alkahestPath}/tests/fixtures/SchemaRegistry.json`);
const TrustedOracleArbiter = await import(`${contractsPath}/TrustedOracleArbiter.json`);
const StringObligation = await import(`${contractsPath}/StringObligation.json`);
const ERC20EscrowObligation = await import(`${contractsPath}/ERC20EscrowObligation.json`);
const ERC20PaymentObligation = await import(`${contractsPath}/ERC20PaymentObligation.json`);
console.log("✅ Contract artifacts loaded\n");
// Deployment addresses
const addresses: Record<string, string> = {};
// Helper to deploy contracts
async function deployContract(
name: string,
abi: any,
bytecode: string,
args: any[] = []
): Promise<string> {
console.log(`📝 Deploying ${name}...`);
const hash = await client.deployContract({
abi,
bytecode: bytecode as `0x${string}`,
args,
});
console.log(` Transaction: ${hash}`);
const receipt = await client.waitForTransactionReceipt({ hash });
if (!receipt.contractAddress) {
throw new Error(`Failed to deploy ${name}`);
}
console.log(` ✅ Deployed at: ${receipt.contractAddress}\n`);
return receipt.contractAddress;
}
// Deploy core contracts
console.log("🏗️ Deploying core contracts...\n");
addresses.easSchemaRegistry = await deployContract(
"EAS Schema Registry",
SchemaRegistry.abi,
SchemaRegistry.bytecode.object
);
addresses.eas = await deployContract(
"EAS",
EAS.abi,
EAS.bytecode.object,
[addresses.easSchemaRegistry]
);
// Deploy arbiters
console.log("⚖️ Deploying arbiters...\n");
addresses.trustedOracleArbiter = await deployContract(
"Trusted Oracle Arbiter",
TrustedOracleArbiter.abi,
TrustedOracleArbiter.bytecode.object,
[addresses.eas]
);
// Deploy obligations
console.log("📋 Deploying obligations...\n");
addresses.stringObligation = await deployContract(
"String Obligation",
StringObligation.abi,
StringObligation.bytecode.object,
[addresses.eas, addresses.easSchemaRegistry]
);
addresses.erc20EscrowObligation = await deployContract(
"ERC20 Escrow Obligation",
ERC20EscrowObligation.abi,
ERC20EscrowObligation.bytecode.object,
[addresses.eas, addresses.easSchemaRegistry]
);
addresses.erc20PaymentObligation = await deployContract(
"ERC20 Payment Obligation",
ERC20PaymentObligation.abi,
ERC20PaymentObligation.bytecode.object,
[addresses.eas, addresses.easSchemaRegistry]
);
// Save deployment addresses
const outputPath = args.output || resolve(`./deployments/${network}.json`);
const outputDir = resolve(outputPath, "..");
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const deployment = {
network,
chainId: chain.id,
rpcUrl,
deployedAt: new Date().toISOString(),
deployer: account.address,
addresses,
};
writeFileSync(outputPath, JSON.stringify(deployment, null, 2));
console.log("✨ Deployment complete!\n");
console.log("📄 Deployment details saved to:", outputPath);
console.log("\n📋 Deployed Addresses:");
Object.entries(addresses).forEach(([name, address]) => {
console.log(` ${name}: ${address}`);
});
console.log("\n🎯 Next steps:");
console.log("1. Save the deployment file for your records");
console.log("2. Update your oracle configuration with the EAS contract address:");
console.log(` export EAS_CONTRACT_ADDRESS=${addresses.eas}`);
console.log("3. Start the oracle:");
console.log(` bun run oracle -- --rpc-url ${rpcUrl} --eas-contract ${addresses.eas}`);
} catch (error) {
console.error("❌ Deployment failed:", error);
process.exit(1);
}
}
// Run the deployment
main();

243
setups/oracle.ts Normal file
View File

@ -0,0 +1,243 @@
#!/usr/bin/env bun
import { parseArgs } from "util";
import { parseAbiParameters } from "viem";
import { makeLLMClient } from "../clients/nla";
import { existsSync, readFileSync } from "fs";
import { resolve } from "path";
// Helper function to display usage
function displayHelp() {
console.log(`
Natural Language Agreement Oracle CLI
Usage:
bun oracle.ts [options]
Options:
--rpc-url <url> RPC URL for the blockchain network (required)
--private-key <key> Private key of the oracle operator (required)
--openai-api-key <key> OpenAI API key (required)
--eas-contract <address> EAS contract address (optional)
--deployment <file> Load addresses from deployment file (optional)
--polling-interval <ms> Polling interval in milliseconds (default: 5000)
--help, -h Display this help message
Environment Variables (alternative to CLI options):
RPC_URL RPC URL for the blockchain network
ORACLE_PRIVATE_KEY Private key of the oracle operator
OPENAI_API_KEY OpenAI API key
EAS_CONTRACT_ADDRESS EAS contract address
Examples:
# Using command line options
bun oracle.ts --rpc-url http://localhost:8545 --private-key 0x... --openai-api-key sk-...
# Using deployment file
bun oracle.ts --deployment ./deployments/localhost.json --private-key 0x... --openai-api-key sk-...
# Using environment variables
export OPENAI_API_KEY=sk-...
export RPC_URL=http://localhost:8545
export ORACLE_PRIVATE_KEY=0x...
bun oracle.ts
`);
}
// Parse command line arguments
function parseCliArgs() {
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
"rpc-url": { type: "string" },
"private-key": { type: "string" },
"openai-api-key": { type: "string" },
"eas-contract": { type: "string" },
"deployment": { type: "string" },
"polling-interval": { type: "string" },
"help": { type: "boolean", short: "h" },
},
strict: true,
});
return values;
}
// Load deployment file
function loadDeployment(filePath: string) {
if (!existsSync(filePath)) {
throw new Error(`Deployment file not found: ${filePath}`);
}
const content = readFileSync(filePath, "utf-8");
return JSON.parse(content);
}
// Main function
async function main() {
try {
const args = parseCliArgs();
// Display help if requested
if (args.help) {
displayHelp();
process.exit(0);
}
let rpcUrl = args["rpc-url"] || process.env.RPC_URL;
let easContract = args["eas-contract"] || process.env.EAS_CONTRACT_ADDRESS;
let deploymentAddresses = null;
// Load deployment file if provided
if (args.deployment) {
console.log(`📄 Loading deployment from: ${args.deployment}\n`);
const deployment = loadDeployment(args.deployment);
deploymentAddresses = deployment.addresses;
if (!rpcUrl) {
rpcUrl = deployment.rpcUrl;
}
if (!easContract) {
easContract = deployment.addresses.eas;
}
console.log(`✅ Loaded deployment (${deployment.network})\n`);
}
const privateKey = args["private-key"] || process.env.ORACLE_PRIVATE_KEY;
const openaiApiKey = args["openai-api-key"] || process.env.OPENAI_API_KEY;
const pollingInterval = parseInt(args["polling-interval"] || "5000");
// Validate required parameters
if (!rpcUrl) {
console.error("❌ Error: RPC URL is required. Use --rpc-url or set RPC_URL environment variable.");
console.error("Run with --help for usage information.");
process.exit(1);
}
if (!privateKey) {
console.error("❌ Error: Private key is required. Use --private-key or set ORACLE_PRIVATE_KEY environment variable.");
console.error("Run with --help for usage information.");
process.exit(1);
}
if (!openaiApiKey) {
console.error("❌ Error: OpenAI API key is required. Use --openai-api-key or set OPENAI_API_KEY environment variable.");
console.error("Run with --help for usage information.");
process.exit(1);
}
// Import alkahest client
const { makeClient } = await import("../../alkahest/sdks/ts/src/index.ts");
const { createWalletClient, http, publicActions } = await import("viem");
const { privateKeyToAccount } = await import("viem/accounts");
const { foundry } = await import("viem/chains");
console.log("🚀 Starting Natural Language Agreement Oracle...\n");
console.log("Configuration:");
console.log(` 📡 RPC URL: ${rpcUrl}`);
console.log(` 🔑 Oracle Key: ${privateKey.slice(0, 6)}...${privateKey.slice(-4)}`);
console.log(` 🤖 AI Provider: OpenAI`);
if (easContract) {
console.log(` 📝 EAS Contract: ${easContract}`);
}
console.log(` ⏱️ Polling Interval: ${pollingInterval}ms\n`);
// Create wallet client
const account = privateKeyToAccount(privateKey as `0x${string}`);
const walletClient = createWalletClient({
account,
chain: foundry,
transport: http(rpcUrl),
}).extend(publicActions) as any;
// Create alkahest client
const client = makeClient(walletClient, deploymentAddresses || { eas: easContract });
console.log(`✅ Oracle initialized with address: ${account.address}\n`);
// Create LLM client
const llmClient = client.extend(() => ({
llm: makeLLMClient([]),
}));
llmClient.llm.addProvider({
providerName: "OpenAI",
apiKey: openaiApiKey,
});
console.log("🎯 LLM Arbitrator configured and ready\n");
console.log("👂 Listening for arbitration requests...\n");
// Define the obligation ABI
const obligationAbi = parseAbiParameters("(string item)");
// Start listening and arbitrating
const { unwatch } = await client.oracle.listenAndArbitrate(
async (attestation: any) => {
console.log(`\n📨 New arbitration request received!`);
console.log(` Attestation UID: ${attestation.uid}`);
try {
// Extract obligation data
const obligation = client.extractObligationData(
obligationAbi,
attestation,
);
console.log(` Obligation: "${obligation[0].item}"`);
// Get demand data
const [, demand] = await client.getEscrowAndDemand(
llmClient.llm.LLMAbi,
attestation,
);
console.log(` Demand: "${demand[0].demand}"`);
console.log(` Model: ${demand[0].arbitrationModel}`);
// Perform arbitration using LLM
console.log(` 🤔 Arbitrating with AI...`);
const result = await llmClient.llm.arbitrate(
demand[0],
obligation[0].item
);
console.log(` ✨ Arbitration result: ${result ? "✅ APPROVED" : "❌ REJECTED"}`);
return result;
} catch (error) {
console.error(` ❌ Error during arbitration:`, error);
throw error;
}
},
{
onAfterArbitrate: async (decision: any) => {
console.log(` 📝 Arbitration decision recorded on-chain`);
console.log(` Decision UID: ${decision.attestation.uid}`);
console.log(` Result: ${decision.decision ? "✅ Fulfilled" : "❌ Not Fulfilled"}\n`);
},
pollingInterval,
},
);
console.log("✨ Oracle is now running. Press Ctrl+C to stop.\n");
// Handle graceful shutdown
const shutdown = async () => {
console.log("\n\n🛑 Shutting down oracle...");
unwatch();
console.log("👋 Oracle stopped gracefully");
process.exit(0);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
// Keep the process alive
await new Promise(() => { });
} catch (error) {
console.error("❌ Fatal error:", error);
process.exit(1);
}
}
// Run the CLI
main();