Merge pull request #1 from arkhai-io/refactor
Refactor: Move nla.ts, Update sample command, Support other AI providers, Update new SDK version
This commit is contained in:
commit
3eb8b35122
14
.env.example
14
.env.example
|
|
@ -34,7 +34,19 @@ ORACLE_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b7
|
|||
# ================================
|
||||
# AI CONFIGURATION
|
||||
# ================================
|
||||
# At least ONE of the following API keys is required for the oracle to function
|
||||
|
||||
# OpenAI API Key (required for oracle)
|
||||
# OpenAI API Key (optional)
|
||||
# Get your API key from https://platform.openai.com/api-keys
|
||||
# Supports models: gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-3.5-turbo
|
||||
OPENAI_API_KEY=sk-proj...
|
||||
|
||||
# Anthropic API Key (optional)
|
||||
# Get your API key from https://console.anthropic.com/
|
||||
# Supports models: claude-3-5-sonnet-20241022, claude-3-opus-20240229, claude-3-sonnet-20240229
|
||||
# ANTHROPIC_API_KEY=sk-ant-...
|
||||
|
||||
# OpenRouter API Key (optional)
|
||||
# Get your API key from https://openrouter.ai/keys
|
||||
# Supports any model available on OpenRouter (e.g., openai/gpt-4, anthropic/claude-3-opus)
|
||||
# OPENROUTER_API_KEY=sk-or-...
|
||||
|
|
|
|||
|
|
@ -39,3 +39,6 @@ anvil.log
|
|||
.DS_Store
|
||||
|
||||
.env
|
||||
|
||||
# Local deployment files
|
||||
cli/deployments/localhost.json
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"Tierable"
|
||||
]
|
||||
}
|
||||
156
README.md
156
README.md
|
|
@ -2,46 +2,29 @@
|
|||
|
||||
## Prerequisites
|
||||
|
||||
This project depends on the `alkahest` repository, which must be cloned in the same parent directory.
|
||||
- [Bun](https://bun.sh) - Fast all-in-one JavaScript runtime
|
||||
- [Foundry](https://book.getfoundry.sh/getting-started/installation) - Ethereum development toolkit (for Anvil)
|
||||
|
||||
### Setup Instructions
|
||||
|
||||
1. **Clone both repositories in the same parent directory:**
|
||||
1. **Clone the repository:**
|
||||
|
||||
```bash
|
||||
# Navigate to your projects directory
|
||||
cd ~/Desktop # or your preferred location
|
||||
|
||||
# Clone the alkahest repository
|
||||
git clone https://github.com/arkhai-io/alkahest.git
|
||||
|
||||
# Clone this repository
|
||||
git clone https://github.com/arkhai-io/natural-language-agreements.git
|
||||
|
||||
# Your directory structure should look like:
|
||||
# parent-directory/
|
||||
# ├── alkahest/
|
||||
# │ └── sdks/
|
||||
# │ └── ts/
|
||||
# └── natural-language-agreements/
|
||||
```
|
||||
|
||||
2. **Install alkahest dependencies:**
|
||||
|
||||
```bash
|
||||
cd alkahest
|
||||
bun install
|
||||
cd ..
|
||||
```
|
||||
|
||||
3. **Install this project's dependencies:**
|
||||
|
||||
```bash
|
||||
cd natural-language-agreements
|
||||
```
|
||||
|
||||
2. **Install dependencies:**
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
4. **Install the `nla` CLI globally (optional but recommended):**
|
||||
3. **Install the `nla` CLI globally (optional but recommended):**
|
||||
|
||||
```bash
|
||||
# Link the CLI to make it available globally
|
||||
|
|
@ -110,7 +93,26 @@ Watch the oracle terminal - you'll see it process arbitration requests in real-t
|
|||
|
||||
## CLI Tools
|
||||
|
||||
The `nla` CLI provides a unified interface for all Natural Language Agreement operations.
|
||||
The `nla` CLI provides a unified interface for all Natural Language Agreement operations with support for multiple LLM providers.
|
||||
|
||||
### Supported LLM Providers
|
||||
|
||||
The oracle supports multiple AI providers for arbitration:
|
||||
|
||||
1. **OpenAI** (default)
|
||||
- Models: `gpt-4o`, `gpt-4o-mini`, `gpt-4-turbo`, `gpt-3.5-turbo`
|
||||
- API Key: Get from [OpenAI Platform](https://platform.openai.com/api-keys)
|
||||
- Environment Variable: `OPENAI_API_KEY`
|
||||
|
||||
2. **Anthropic (Claude)**
|
||||
- Models: `claude-3-5-sonnet-20241022`, `claude-3-opus-20240229`, `claude-3-sonnet-20240229`
|
||||
- API Key: Get from [Anthropic Console](https://console.anthropic.com/)
|
||||
- Environment Variable: `ANTHROPIC_API_KEY`
|
||||
|
||||
3. **OpenRouter**
|
||||
- Models: Any model available on OpenRouter (e.g., `openai/gpt-4`, `anthropic/claude-3-opus`)
|
||||
- API Key: Get from [OpenRouter](https://openrouter.ai/keys)
|
||||
- Environment Variable: `OPENROUTER_API_KEY`
|
||||
|
||||
### Installation
|
||||
|
||||
|
|
@ -139,13 +141,22 @@ For a complete guide to all CLI commands and options, see [CLI Documentation](cl
|
|||
### Quick CLI Examples
|
||||
|
||||
```bash
|
||||
# Create an escrow
|
||||
# Create an escrow with OpenAI (default)
|
||||
nla escrow:create \
|
||||
--demand "The sky is blue" \
|
||||
--amount 10 \
|
||||
--token 0xa513e6e4b8f2a923d98304ec87f64353c4d5c853 \
|
||||
--oracle 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
|
||||
|
||||
# Create an escrow with custom arbitration settings
|
||||
nla escrow:create \
|
||||
--demand "Deliver package by Friday" \
|
||||
--amount 100 \
|
||||
--token 0xa513e6e4b8f2a923d98304ec87f64353c4d5c853 \
|
||||
--oracle 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \
|
||||
--arbitration-provider "Anthropic" \
|
||||
--arbitration-model "claude-3-5-sonnet-20241022"
|
||||
|
||||
# Fulfill an escrow
|
||||
nla escrow:fulfill \
|
||||
--escrow-uid 0x... \
|
||||
|
|
@ -212,6 +223,26 @@ nla escrow:status [options] # Check escrow status
|
|||
nla help # Show help
|
||||
```
|
||||
|
||||
### Escrow Creation Options
|
||||
|
||||
When creating an escrow, you can customize the arbitration settings:
|
||||
|
||||
```bash
|
||||
nla escrow:create \
|
||||
--demand "Your natural language demand" \
|
||||
--amount <token-amount> \
|
||||
--token <erc20-token-address> \
|
||||
--oracle <oracle-address> \
|
||||
--arbitration-provider "OpenAI|Anthropic|OpenRouter" \ # Optional, default: OpenAI
|
||||
--arbitration-model "model-name" \ # Optional, default: gpt-4o-mini
|
||||
--arbitration-prompt "Custom prompt template" # Optional
|
||||
```
|
||||
|
||||
**Default Arbitration Settings:**
|
||||
- Provider: `OpenAI`
|
||||
- Model: `gpt-4o-mini`
|
||||
- Prompt: Standard evaluation template
|
||||
|
||||
**NPM Scripts (alternative):**
|
||||
```bash
|
||||
bun run setup # Same as: nla dev
|
||||
|
|
@ -273,12 +304,12 @@ natural-language-agreements/
|
|||
│ ├── index.ts # Main CLI entry point (nla command)
|
||||
│ ├── README.md # CLI documentation
|
||||
│ ├── client/ # User-facing escrow tools
|
||||
│ │ ├── create-escrow.ts # Create escrow CLI
|
||||
│ │ ├── create-escrow.ts # Create escrow CLI with arbitration config
|
||||
│ │ ├── fulfill-escrow.ts # Fulfill escrow CLI
|
||||
│ │ └── collect-escrow.ts # Collect escrow CLI
|
||||
│ ├── server/ # Server-side components
|
||||
│ │ ├── deploy.ts # Contract deployment script
|
||||
│ │ └── oracle.ts # Oracle service
|
||||
│ │ └── oracle.ts # Multi-provider oracle service
|
||||
│ ├── scripts/ # Shell scripts for orchestration
|
||||
│ │ ├── dev.sh # Development environment setup
|
||||
│ │ ├── deploy.sh # Deployment wrapper
|
||||
|
|
@ -288,12 +319,14 @@ natural-language-agreements/
|
|||
│ ├── localhost.json
|
||||
│ ├── sepolia.json
|
||||
│ └── mainnet.json
|
||||
├── clients/
|
||||
│ └── nla.ts # Natural Language Agreement client library
|
||||
├── nla.ts # Natural Language Agreement client library
|
||||
│ # - Multi-provider LLM support
|
||||
│ # - Arbitration encoding/decoding
|
||||
│ # - OpenAI, Anthropic, OpenRouter integration
|
||||
├── tests/
|
||||
│ ├── nla.test.ts # Basic tests
|
||||
│ └── nlaOracle.test.ts # Oracle arbitration tests
|
||||
├── index.ts # Development entry point
|
||||
├── index.ts # Main exports
|
||||
├── package.json
|
||||
└── README.md
|
||||
```
|
||||
|
|
@ -301,8 +334,9 @@ natural-language-agreements/
|
|||
## Troubleshooting
|
||||
|
||||
### "Cannot find module 'alkahest-ts'"
|
||||
- Ensure alkahest is cloned in the parent directory
|
||||
- Run `bun install` in both alkahest and this project
|
||||
- Run `bun install` to ensure all dependencies are installed
|
||||
- Clear the cache: `rm -rf node_modules && bun install`
|
||||
- Check that package.json includes alkahest-ts dependency
|
||||
|
||||
### "Deployer has no ETH"
|
||||
- Fund your deployer account before running deployment
|
||||
|
|
@ -317,7 +351,22 @@ natural-language-agreements/
|
|||
### "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")
|
||||
- Ensure model name is correct (e.g., "gpt-4o-mini", "gpt-4o")
|
||||
|
||||
### "Anthropic API errors"
|
||||
- Verify ANTHROPIC_API_KEY is set correctly
|
||||
- Check Anthropic usage limits and billing
|
||||
- Ensure model name is correct (e.g., "claude-3-5-sonnet-20241022")
|
||||
|
||||
### "Arbitration provider not found"
|
||||
- The oracle was configured with a different provider than the escrow
|
||||
- Make sure the oracle has the correct API keys for the provider specified in the escrow
|
||||
- Supported providers: OpenAI, Anthropic, OpenRouter
|
||||
|
||||
### "Module resolution errors"
|
||||
- Run `bun install` to ensure alkahest-ts is properly installed
|
||||
- Check that you're using the correct version of Bun: `bun --version`
|
||||
- Clear Bun's cache: `rm -rf node_modules && bun install`
|
||||
|
||||
## Security Notes
|
||||
|
||||
|
|
@ -327,5 +376,38 @@ natural-language-agreements/
|
|||
- 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
|
||||
- Keep all API keys secure (OpenAI, Anthropic, OpenRouter):
|
||||
* Don't expose them in logs or error messages
|
||||
* Use environment variables or secure secret management
|
||||
* Rotate keys regularly
|
||||
* Monitor usage for unauthorized access
|
||||
- Run the oracle in a secure environment with proper access controls
|
||||
- For production deployments:
|
||||
* Use hardware wallets or secure key management services
|
||||
* Implement rate limiting on the oracle
|
||||
* Monitor arbitration decisions for anomalies
|
||||
* Consider using a multi-signature setup for critical operations
|
||||
|
||||
## Features
|
||||
|
||||
✨ **Multi-Provider LLM Support**
|
||||
- OpenAI (GPT-4, GPT-4o, GPT-3.5-turbo)
|
||||
- Anthropic (Claude 3 family)
|
||||
- OpenRouter (Access to any model)
|
||||
- Configurable per-escrow arbitration settings
|
||||
|
||||
🔧 **Flexible Configuration**
|
||||
- Custom arbitration prompts
|
||||
- Provider and model selection
|
||||
- Default settings with override capability
|
||||
|
||||
🚀 **Easy Deployment**
|
||||
- One-command development setup (`nla dev`)
|
||||
- Automated contract deployment
|
||||
- Built-in test token distribution
|
||||
|
||||
⚡ **Developer Friendly**
|
||||
- TypeScript support
|
||||
- Comprehensive CLI tools
|
||||
- Unified interface for all operations
|
||||
- Detailed error messages and logging
|
||||
|
|
|
|||
23
bun.lock
23
bun.lock
|
|
@ -5,11 +5,12 @@
|
|||
"": {
|
||||
"name": "natural-language-agreement-extension",
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^2.0.50",
|
||||
"@ai-sdk/anthropic": "^3.0.2",
|
||||
"@ai-sdk/openai": "^3.0.2",
|
||||
"@viem/anvil": "^0.0.10",
|
||||
"ai": "^5.0.68",
|
||||
"alkahest-ts": "git+https://github.com/VAR-META-Tech/alkahest.git#ts-package",
|
||||
"ai": "^6.0.5",
|
||||
"arktype": "^2.1.23",
|
||||
"alkahest-ts": "github:arkhai-io/alkahest",
|
||||
"viem": "^2.42.1",
|
||||
"zod": "^3.25.76",
|
||||
},
|
||||
|
|
@ -24,13 +25,15 @@
|
|||
"packages": {
|
||||
"@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="],
|
||||
|
||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-BwV7DU/lAm3Xn6iyyvZdWgVxgLu3SNXzl5y57gMvkW4nGhAOV5269IrJzQwGt03bb107sa6H6uJwWxc77zXoGA=="],
|
||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.2", "", { "dependencies": { "@ai-sdk/provider": "3.0.1", "@ai-sdk/provider-utils": "4.0.2" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-D6iSsrOYryBSPsFtOiEDv54jnjVCU/flIuXdjuRY7LdikB0KGjpazN8Dt4ONXzL+ux69ds2nzFNKke/w/fgLAA=="],
|
||||
|
||||
"@ai-sdk/openai": ["@ai-sdk/openai@2.0.86", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-obsLIOyA93lbQiSt1rvBItoVQp1U2RDPs0bNG0JYhm6Gku8Dg/0Cm8e4NUWT5p5PN10/doKSb3SMSKCixwIAKA=="],
|
||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.4", "", { "dependencies": { "@ai-sdk/provider": "3.0.1", "@ai-sdk/provider-utils": "4.0.2", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OlccjNYZ5+4FaNyvs0kb3N5H6U/QCKlKPTGsgUo8IZkqfMQu8ALI1XD6l/BCuTKto+OO9xUPObT/W7JhbqJ5nA=="],
|
||||
|
||||
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
||||
"@ai-sdk/openai": ["@ai-sdk/openai@3.0.2", "", { "dependencies": { "@ai-sdk/provider": "3.0.1", "@ai-sdk/provider-utils": "4.0.2" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-GONwavgSWtcWO+t9+GpGK8l7nIYh+zNtCL/NYDSeHxHiw6ksQS9XMRWrZyE5NpJ0EXNxSAWCHIDmb1WvTqhq9Q=="],
|
||||
|
||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="],
|
||||
"@ai-sdk/provider": ["@ai-sdk/provider@3.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-2lR4w7mr9XrydzxBSjir4N6YMGdXD+Np1Sh0RXABh7tWdNFFwIeRI1Q+SaYZMbfL8Pg8RRLcrxQm51yxTLhokg=="],
|
||||
|
||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.2", "", { "dependencies": { "@ai-sdk/provider": "3.0.1", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-KaykkuRBdF/ffpI5bwpL4aSCmO/99p8/ci+VeHwJO8tmvXtiVAb99QeyvvvXmL61e9Zrvv4GBGoajW19xdjkVQ=="],
|
||||
|
||||
"@ark/schema": ["@ark/schema@0.56.0", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA=="],
|
||||
|
||||
|
|
@ -50,7 +53,7 @@
|
|||
|
||||
"@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
|
||||
|
||||
|
|
@ -62,9 +65,9 @@
|
|||
|
||||
"abitype": ["abitype@1.1.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A=="],
|
||||
|
||||
"ai": ["ai@5.0.113", "", { "dependencies": { "@ai-sdk/gateway": "2.0.21", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-26vivpSO/mzZj0k1Si2IpsFspp26ttQICHRySQiMrtWcRd5mnJMX2a8sG28vmZ38C+JUn1cWmfZrsLMxkSMw9g=="],
|
||||
"ai": ["ai@6.0.5", "", { "dependencies": { "@ai-sdk/gateway": "3.0.4", "@ai-sdk/provider": "3.0.1", "@ai-sdk/provider-utils": "4.0.2", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CKL3dDHedWskC6EY67LrULonZBU9vL+Bwa+xQEcprBhJfxpogntG3utjiAkYuy5ZQatyWk+SmWG8HLvcnhvbRg=="],
|
||||
|
||||
"alkahest-ts": ["alkahest-ts@github:VAR-META-Tech/alkahest#d40de37", {}, "VAR-META-Tech-alkahest-d40de37"],
|
||||
"alkahest-ts": ["alkahest-ts@github:arkhai-io/alkahest#5df4180", {}, "arkhai-io-alkahest-5df4180"],
|
||||
|
||||
"arkregex": ["arkregex@0.0.5", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ async function main() {
|
|||
console.log("💰 Collecting escrow...\n");
|
||||
|
||||
// Collect the escrow
|
||||
const collectionHash = await client.erc20.collectEscrow(
|
||||
const collectionHash = await client.erc20.escrow.nonTierable.collect(
|
||||
escrowUid as `0x${string}`,
|
||||
fulfillmentUid as `0x${string}`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { foundry } from "viem/chains";
|
|||
import { existsSync, readFileSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { makeClient } from "alkahest-ts";
|
||||
import { makeLLMClient } from "../../clients/nla.ts";
|
||||
import { makeLLMClient } from "../..";
|
||||
import {fixtures} from "alkahest-ts";
|
||||
|
||||
// Helper function to display usage
|
||||
|
|
@ -34,6 +34,9 @@ Options:
|
|||
--private-key <key> Your private key (required)
|
||||
--deployment <path> Path to deployment file (default: ./cli/deployments/localhost.json)
|
||||
--rpc-url <url> RPC URL (default: from deployment file)
|
||||
--arbitration-provider <name> Arbitration provider (default: OpenAI)
|
||||
--arbitration-model <model> Arbitration model (default: gpt-4o-mini)
|
||||
--arbitration-prompt <text> Custom arbitration prompt (optional)
|
||||
--help, -h Display this help message
|
||||
|
||||
Environment Variables (alternative to CLI options):
|
||||
|
|
@ -67,6 +70,9 @@ function parseCliArgs() {
|
|||
"private-key": { type: "string" },
|
||||
"deployment": { type: "string" },
|
||||
"rpc-url": { type: "string" },
|
||||
"arbitration-provider": { type: "string" },
|
||||
"arbitration-model": { type: "string" },
|
||||
"arbitration-prompt": { type: "string" },
|
||||
"help": { type: "boolean", short: "h" },
|
||||
},
|
||||
strict: true,
|
||||
|
|
@ -92,6 +98,16 @@ async function main() {
|
|||
const oracleAddress = args.oracle;
|
||||
const privateKey = args["private-key"] || process.env.PRIVATE_KEY;
|
||||
const deploymentPath = args.deployment || "./cli/deployments/localhost.json";
|
||||
|
||||
// Arbitration configuration with defaults
|
||||
const arbitrationProvider = args["arbitration-provider"] || "OpenAI";
|
||||
const arbitrationModel = args["arbitration-model"] || "gpt-4o-mini";
|
||||
const arbitrationPrompt = args["arbitration-prompt"] ||
|
||||
`Evaluate the fulfillment against the demand and decide whether the demand was validly fulfilled
|
||||
|
||||
Demand: {{demand}}
|
||||
|
||||
Fulfillment: {{obligation}}`;
|
||||
|
||||
// Validate required parameters
|
||||
if (!demand) {
|
||||
|
|
@ -167,17 +183,11 @@ async function main() {
|
|||
deployment.addresses
|
||||
);
|
||||
|
||||
// Extend with LLM client
|
||||
// Extend with LLM client (only for encoding the demand, no API calls needed)
|
||||
const llmClient = client.extend((c) => ({
|
||||
llm: makeLLMClient([]),
|
||||
}));
|
||||
|
||||
// Add OpenAI provider (needed for encoding demands)
|
||||
llmClient.llm.addProvider({
|
||||
providerName: "OpenAI",
|
||||
apiKey: process.env.OPENAI_API_KEY || "",
|
||||
});
|
||||
|
||||
// Check token balance
|
||||
const tokenBalance = await walletClient.readContract({
|
||||
address: tokenAddress as `0x${string}`,
|
||||
|
|
@ -197,22 +207,18 @@ async function main() {
|
|||
|
||||
// Encode the demand with oracle arbiter
|
||||
const arbiter = deployment.addresses.trustedOracleArbiter;
|
||||
const encodedDemand = client.arbiters.general.trustedOracle.encode({
|
||||
const encodedDemand = client.arbiters.general.trustedOracle.encodeDemand({
|
||||
oracle: oracleAddress as `0x${string}`,
|
||||
data: llmClient.llm.encodeDemand({
|
||||
arbitrationProvider: "OpenAI",
|
||||
arbitrationModel: "gpt-4.1",
|
||||
arbitrationPrompt: `Evaluate the fulfillment against the demand and decide whether the demand was validly fulfilled
|
||||
|
||||
Demand: {{demand}}
|
||||
|
||||
Fulfillment: {{obligation}}`,
|
||||
arbitrationProvider,
|
||||
arbitrationModel,
|
||||
arbitrationPrompt,
|
||||
demand: demand
|
||||
})
|
||||
});
|
||||
|
||||
// Create the escrow
|
||||
const { attested: escrow } = await client.erc20.permitAndBuyWithErc20(
|
||||
const { attested: escrow } = await client.erc20.escrow.nonTierable.permitAndCreate(
|
||||
{
|
||||
address: tokenAddress as `0x${string}`,
|
||||
value: BigInt(amount),
|
||||
|
|
@ -228,10 +234,16 @@ Fulfillment: {{obligation}}`,
|
|||
console.log(` Recipient: ${escrow.recipient}`);
|
||||
|
||||
console.log("🎯 Next Steps:");
|
||||
console.log("1. Wait for someone to fulfill the obligation");
|
||||
console.log("2. The oracle will arbitrate the fulfillment");
|
||||
console.log("3. If approved, you can collect the escrow");
|
||||
console.log(`\n Escrow UID: ${escrow.uid}`);
|
||||
console.log("1. Someone fulfills the obligation:");
|
||||
console.log(` nla escrow:fulfill \\`);
|
||||
console.log(` --escrow-uid ${escrow.uid} \\`);
|
||||
console.log(` --fulfillment "Yes, the sky is blue" \\`);
|
||||
console.log(` --oracle ${oracleAddress}`);
|
||||
console.log("\n2. The oracle will arbitrate the fulfillment automatically");
|
||||
console.log("\n3. If approved, collect the escrow:");
|
||||
console.log(` nla escrow:collect \\`);
|
||||
console.log(` --escrow-uid ${escrow.uid} \\`);
|
||||
console.log(` --fulfillment-uid <fulfillment-uid>`);
|
||||
|
||||
} catch (error) {
|
||||
console.error("❌ Failed to create escrow:", error);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { foundry } from "viem/chains";
|
|||
import { existsSync, readFileSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { makeClient } from "alkahest-ts";
|
||||
import { makeLLMClient } from "../..";
|
||||
|
||||
// Helper function to display usage
|
||||
function displayHelp() {
|
||||
|
|
@ -161,26 +162,28 @@ async function main() {
|
|||
fulfillment,
|
||||
escrowUid as `0x${string}`,
|
||||
);
|
||||
|
||||
console.log("✅ Fulfillment created!\n");
|
||||
console.log("📋 Fulfillment Details:");
|
||||
console.log(` UID: ${fulfillmentAttestation.uid}`);
|
||||
console.log(` Attester: ${fulfillmentAttestation.attester}\n`);
|
||||
|
||||
console.log("📤 Requesting arbitration from oracle...\n");
|
||||
|
||||
const escrow = await client.getAttestation(escrowUid as `0x${string}`);
|
||||
const decodedEscrow = client.erc20.escrow.nonTierable.decodeObligation(escrow.data);
|
||||
// Request arbitration
|
||||
await client.oracle.requestArbitration(
|
||||
await client.arbiters.general.trustedOracle.requestArbitration(
|
||||
fulfillmentAttestation.uid,
|
||||
oracleAddress as `0x${string}`,
|
||||
decodedEscrow.demand
|
||||
);
|
||||
|
||||
console.log("✨ Arbitration requested successfully!\n");
|
||||
console.log("🎯 Next Steps:");
|
||||
console.log("1. Wait for the oracle to arbitrate (usually a few seconds)");
|
||||
console.log("2. Check the result with the oracle");
|
||||
console.log("3. If approved, collect the escrow");
|
||||
console.log(`\n Fulfillment UID: ${fulfillmentAttestation.uid}`);
|
||||
console.log("\n2. If approved, collect the escrow:");
|
||||
console.log(` nla escrow:collect \\`);
|
||||
console.log(` --escrow-uid ${escrowUid} \\`);
|
||||
console.log(` --fulfillment-uid ${fulfillmentAttestation.uid}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error("❌ Failed to fulfill escrow:", error);
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"network": "localhost",
|
||||
"chainId": 31337,
|
||||
"rpcUrl": "http://localhost:8545",
|
||||
"deployedAt": "2025-12-15T17:03:01.822Z",
|
||||
"deployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
|
||||
"addresses": {
|
||||
"easSchemaRegistry": "0x5fbdb2315678afecb367f032d93f642f64180aa3",
|
||||
"eas": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512",
|
||||
"trustedOracleArbiter": "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0",
|
||||
"stringObligation": "0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9",
|
||||
"erc20EscrowObligation": "0xdc64a140aa3e981100a9beca4e685f962f0cf6c9",
|
||||
"erc20PaymentObligation": "0x5fc8d32690cc91d4c39d9d3abcbd16989f875707",
|
||||
"erc20BarterUtils": "0x0165878a594ca255338adfa4d48449f69242eb8f",
|
||||
"mockERC20A": "0xa513e6e4b8f2a923d98304ec87f64353c4d5c853",
|
||||
"mockERC20B": "0x2279b7a0a67db372996a5fab50d91eaa73d2ebe6",
|
||||
"mockERC20C": "0x8a791620dd6260079bf849dc5567adc3f2fdc318"
|
||||
}
|
||||
}
|
||||
14
cli/index.ts
14
cli/index.ts
|
|
@ -36,6 +36,9 @@ Options (vary by command):
|
|||
--private-key <key> Private key (all commands)
|
||||
--rpc-url <url> RPC URL (default: http://localhost:8545)
|
||||
--deployment <file> Load addresses from deployment file
|
||||
--arbitration-provider <name> Arbitration provider (create, default: OpenAI)
|
||||
--arbitration-model <model> Arbitration model (create, default: gpt-4o-mini)
|
||||
--arbitration-prompt <text> Custom arbitration prompt (create, optional)
|
||||
|
||||
Environment Variables:
|
||||
PRIVATE_KEY Private key for transactions
|
||||
|
|
@ -107,6 +110,9 @@ function parseCliArgs() {
|
|||
"private-key": { type: "string" },
|
||||
"rpc-url": { type: "string" },
|
||||
"deployment": { type: "string" },
|
||||
"arbitration-provider": { type: "string" },
|
||||
"arbitration-model": { type: "string" },
|
||||
"arbitration-prompt": { type: "string" },
|
||||
},
|
||||
strict: true,
|
||||
});
|
||||
|
|
@ -261,7 +267,7 @@ async function runStatusCommand(args: any) {
|
|||
|
||||
const escrow = await publicClient.readContract({
|
||||
address: addresses.eas,
|
||||
abi: contracts.IEAS.abi,
|
||||
abi: contracts.IEAS.abi.abi,
|
||||
functionName: "getAttestation",
|
||||
args: [escrowUid],
|
||||
}) as any;
|
||||
|
|
@ -292,7 +298,7 @@ async function runStatusCommand(args: any) {
|
|||
|
||||
const filter = await publicClient.createContractEventFilter({
|
||||
address: addresses.eas,
|
||||
abi: contracts.IEAS.abi,
|
||||
abi: contracts.IEAS.abi.abi,
|
||||
eventName: "Attested",
|
||||
fromBlock: 0n,
|
||||
});
|
||||
|
|
@ -313,7 +319,7 @@ async function runStatusCommand(args: any) {
|
|||
const fulfillmentUid = (fulfillment as any).args?.uid;
|
||||
const fulfillmentAttestation = await publicClient.readContract({
|
||||
address: addresses.eas,
|
||||
abi: contracts.IEAS.abi,
|
||||
abi: contracts.IEAS.abi.abi,
|
||||
functionName: "getAttestation",
|
||||
args: [fulfillmentUid],
|
||||
}) as any;
|
||||
|
|
@ -330,7 +336,7 @@ async function runStatusCommand(args: any) {
|
|||
const decisionUid = (decision as any).args?.uid;
|
||||
const decisionAttestation = await publicClient.readContract({
|
||||
address: addresses.eas,
|
||||
abi: contracts.IEAS.abi,
|
||||
abi: contracts.IEAS.abi.abi,
|
||||
functionName: "getAttestation",
|
||||
args: [decisionUid],
|
||||
}) as any;
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ async function main() {
|
|||
const StringObligation = contracts.StringObligation;
|
||||
const ERC20EscrowObligation = contracts.ERC20EscrowObligation;
|
||||
const ERC20PaymentObligation = contracts.ERC20PaymentObligation;
|
||||
const ERC20BarterCrossToken = contracts.ERC20BarterCrossToken;
|
||||
const ERC20BarterUtils = contracts.ERC20BarterUtils;
|
||||
|
||||
console.log("✅ Contract artifacts loaded\n");
|
||||
|
||||
|
|
@ -213,8 +213,8 @@ async function main() {
|
|||
|
||||
addresses.trustedOracleArbiter = await deployContract(
|
||||
"Trusted Oracle Arbiter",
|
||||
TrustedOracleArbiter.abi,
|
||||
TrustedOracleArbiter.bytecode.object,
|
||||
TrustedOracleArbiter.abi.abi,
|
||||
TrustedOracleArbiter.abi.bytecode.object,
|
||||
[addresses.eas]
|
||||
);
|
||||
|
||||
|
|
@ -223,32 +223,31 @@ async function main() {
|
|||
|
||||
addresses.stringObligation = await deployContract(
|
||||
"String Obligation",
|
||||
StringObligation.abi,
|
||||
StringObligation.bytecode.object,
|
||||
StringObligation.abi.abi,
|
||||
StringObligation.abi.bytecode.object,
|
||||
[addresses.eas, addresses.easSchemaRegistry]
|
||||
);
|
||||
|
||||
addresses.erc20EscrowObligation = await deployContract(
|
||||
"ERC20 Escrow Obligation",
|
||||
ERC20EscrowObligation.abi,
|
||||
ERC20EscrowObligation.bytecode.object,
|
||||
ERC20EscrowObligation.abi.abi,
|
||||
ERC20EscrowObligation.abi.bytecode.object,
|
||||
[addresses.eas, addresses.easSchemaRegistry]
|
||||
);
|
||||
|
||||
addresses.erc20PaymentObligation = await deployContract(
|
||||
"ERC20 Payment Obligation",
|
||||
ERC20PaymentObligation.abi,
|
||||
ERC20PaymentObligation.bytecode.object,
|
||||
ERC20PaymentObligation.abi.abi,
|
||||
ERC20PaymentObligation.abi.bytecode.object,
|
||||
[addresses.eas, addresses.easSchemaRegistry]
|
||||
);
|
||||
|
||||
// Deploy barter utils (required for permitAndBuyWithErc20)
|
||||
console.log("🔄 Deploying barter utils...\n");
|
||||
|
||||
addresses.erc20BarterUtils = await deployContract(
|
||||
"ERC20 Barter Utils",
|
||||
ERC20BarterCrossToken.abi,
|
||||
ERC20BarterCrossToken.bytecode.object,
|
||||
ERC20BarterUtils.abi.abi,
|
||||
ERC20BarterUtils.abi.bytecode.object,
|
||||
[
|
||||
addresses.eas,
|
||||
addresses.erc20EscrowObligation,
|
||||
|
|
@ -259,6 +258,8 @@ async function main() {
|
|||
"0x0000000000000000000000000000000000000000", // erc1155Payment (not used)
|
||||
"0x0000000000000000000000000000000000000000", // tokenBundleEscrow (not used)
|
||||
"0x0000000000000000000000000000000000000000", // tokenBundlePayment (not used)
|
||||
"0x0000000000000000000000000000000000000000", // nativeEscrow (not used)
|
||||
"0x0000000000000000000000000000000000000000", // nativePayment (not used)
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -363,9 +364,17 @@ async function main() {
|
|||
|
||||
console.log("\n🎯 Next steps:");
|
||||
console.log("1. Start the oracle:");
|
||||
console.log(" ./scripts/start-oracle.sh " + network);
|
||||
console.log("\n2. Create an escrow:");
|
||||
console.log(` bun run escrow:create --demand "Your demand" --amount 10 --token ${addresses.mockERC20A} --oracle 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --private-key 0xac09...`);
|
||||
console.log(` nla start-oracle`);
|
||||
console.log("\n2. Export your private key (use a test account private key):");
|
||||
console.log(` export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80`);
|
||||
console.log("\n3. Create an escrow:");
|
||||
console.log(` nla escrow:create \\`);
|
||||
console.log(` --demand "The sky is blue" \\`);
|
||||
console.log(` --amount 10 \\`);
|
||||
console.log(` --token ${addresses.mockERC20A} \\`);
|
||||
console.log(` --oracle 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \\`);
|
||||
console.log(` --arbitration-provider "OpenAI" \\`);
|
||||
console.log(` --arbitration-model "gpt-4o-mini"`);
|
||||
|
||||
} catch (error) {
|
||||
console.error("❌ Deployment failed:", error);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { parseArgs } from "util";
|
|||
import { parseAbiParameters, createWalletClient, http, publicActions } from "viem";
|
||||
import { privateKeyToAccount } from "viem/accounts";
|
||||
import { foundry } from "viem/chains";
|
||||
import { makeLLMClient } from "../../clients/nla";
|
||||
import { makeLLMClient } from "../..";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { makeClient } from "alkahest-ts";
|
||||
|
|
@ -20,7 +20,9 @@ Usage:
|
|||
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)
|
||||
--openai-api-key <key> OpenAI API key (optional)
|
||||
--anthropic-api-key <key> Anthropic API key (optional)
|
||||
--openrouter-api-key <key> OpenRouter API key (optional)
|
||||
--eas-contract <address> EAS contract address (optional)
|
||||
--deployment <file> Load addresses from deployment file (optional)
|
||||
--polling-interval <ms> Polling interval in milliseconds (default: 5000)
|
||||
|
|
@ -30,6 +32,8 @@ 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
|
||||
ANTHROPIC_API_KEY Anthropic API key
|
||||
OPENROUTER_API_KEY OpenRouter API key
|
||||
EAS_CONTRACT_ADDRESS EAS contract address
|
||||
|
||||
Examples:
|
||||
|
|
@ -55,6 +59,8 @@ function parseCliArgs() {
|
|||
"rpc-url": { type: "string" },
|
||||
"private-key": { type: "string" },
|
||||
"openai-api-key": { type: "string" },
|
||||
"anthropic-api-key": { type: "string" },
|
||||
"openrouter-api-key": { type: "string" },
|
||||
"eas-contract": { type: "string" },
|
||||
"deployment": { type: "string" },
|
||||
"polling-interval": { type: "string" },
|
||||
|
|
@ -109,6 +115,8 @@ async function main() {
|
|||
|
||||
const privateKey = args["private-key"] || process.env.ORACLE_PRIVATE_KEY;
|
||||
const openaiApiKey = args["openai-api-key"] || process.env.OPENAI_API_KEY;
|
||||
const anthropicApiKey = args["anthropic-api-key"] || process.env.ANTHROPIC_API_KEY;
|
||||
const openrouterApiKey = args["openrouter-api-key"] || process.env.OPENROUTER_API_KEY;
|
||||
const pollingInterval = parseInt(args["polling-interval"] || "5000");
|
||||
|
||||
// Validate required parameters
|
||||
|
|
@ -124,8 +132,10 @@ async function main() {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!openaiApiKey) {
|
||||
console.error("❌ Error: OpenAI API key is required. Use --openai-api-key or set OPENAI_API_KEY environment variable.");
|
||||
// Check if at least one API key is provided
|
||||
if (!openaiApiKey && !anthropicApiKey && !openrouterApiKey) {
|
||||
console.error("❌ Error: At least one LLM provider API key is required.");
|
||||
console.error(" Set one of: OPENAI_API_KEY, ANTHROPIC_API_KEY, or OPENROUTER_API_KEY");
|
||||
console.error("Run with --help for usage information.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
@ -134,7 +144,14 @@ async function main() {
|
|||
console.log("Configuration:");
|
||||
console.log(` 📡 RPC URL: ${rpcUrl}`);
|
||||
console.log(` 🔑 Oracle Key: ${privateKey.slice(0, 6)}...${privateKey.slice(-4)}`);
|
||||
console.log(` 🤖 AI Provider: OpenAI`);
|
||||
|
||||
// Show available providers
|
||||
const availableProviders = [];
|
||||
if (openaiApiKey) availableProviders.push("OpenAI");
|
||||
if (anthropicApiKey) availableProviders.push("Anthropic");
|
||||
if (openrouterApiKey) availableProviders.push("OpenRouter");
|
||||
console.log(` 🤖 AI Providers: ${availableProviders.join(", ")}`);
|
||||
|
||||
if (easContract) {
|
||||
console.log(` 📝 EAS Contract: ${easContract}`);
|
||||
}
|
||||
|
|
@ -158,19 +175,39 @@ async function main() {
|
|||
llm: makeLLMClient([]),
|
||||
}));
|
||||
|
||||
llmClient.llm.addProvider({
|
||||
providerName: "OpenAI",
|
||||
apiKey: openaiApiKey,
|
||||
});
|
||||
// Add all available providers
|
||||
if (openaiApiKey) {
|
||||
llmClient.llm.addProvider({
|
||||
providerName: "OpenAI",
|
||||
apiKey: openaiApiKey,
|
||||
});
|
||||
console.log("✅ OpenAI provider configured");
|
||||
}
|
||||
|
||||
console.log("🎯 LLM Arbitrator configured and ready\n");
|
||||
if (anthropicApiKey) {
|
||||
llmClient.llm.addProvider({
|
||||
providerName: "Anthropic",
|
||||
apiKey: anthropicApiKey,
|
||||
});
|
||||
console.log("✅ Anthropic provider configured");
|
||||
}
|
||||
|
||||
if (openrouterApiKey) {
|
||||
llmClient.llm.addProvider({
|
||||
providerName: "OpenRouter",
|
||||
apiKey: openrouterApiKey,
|
||||
});
|
||||
console.log("✅ OpenRouter provider configured");
|
||||
}
|
||||
|
||||
console.log("\n🎯 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(
|
||||
const { unwatch } = await client.arbiters.general.trustedOracle.listenAndArbitrate(
|
||||
async (attestation: any) => {
|
||||
console.log(`\n📨 New arbitration request received!`);
|
||||
console.log(` Attestation UID: ${attestation.uid}`);
|
||||
|
|
|
|||
|
|
@ -1,98 +0,0 @@
|
|||
import {
|
||||
decodeAbiParameters,
|
||||
encodeAbiParameters,
|
||||
parseAbiParameters,
|
||||
} from "viem";
|
||||
import { generateText } from "ai";
|
||||
import { createOpenAI } from "@ai-sdk/openai";
|
||||
|
||||
|
||||
export type LLMProvider = {
|
||||
providerName: string;
|
||||
apiKey?: string;
|
||||
};
|
||||
|
||||
export type LLMDemand = {
|
||||
arbitrationProvider: string;
|
||||
arbitrationModel: string;
|
||||
arbitrationPrompt: string;
|
||||
demand: string;
|
||||
};
|
||||
|
||||
export const makeLLMClient = (
|
||||
providers: LLMProvider[],
|
||||
) => {
|
||||
|
||||
const LLMAbi = parseAbiParameters(
|
||||
"(string arbitrationProvider, string arbitrationModel, string arbitrationPrompt, string demand)",
|
||||
);
|
||||
const encodeDemand = (demand: LLMDemand) => {
|
||||
return encodeAbiParameters(
|
||||
LLMAbi,
|
||||
[demand],
|
||||
);
|
||||
};
|
||||
|
||||
const arbitrate = async (demand: LLMDemand, obligation: string): Promise<boolean> => {
|
||||
try {
|
||||
const matchingProvider = providers.find(provider =>
|
||||
provider.providerName.toLowerCase().includes(demand.arbitrationProvider.toLowerCase()) ||
|
||||
demand.arbitrationProvider.toLowerCase().includes(provider.providerName.toLowerCase())
|
||||
);
|
||||
|
||||
const selectedProvider = matchingProvider || providers[0];
|
||||
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No LLM provider available');
|
||||
}
|
||||
|
||||
console.log(`Using provider: ${selectedProvider.providerName} for arbitration demand: ${JSON.stringify(demand)}`);
|
||||
if (selectedProvider.providerName.toLowerCase() === 'openai') {
|
||||
|
||||
const openai = createOpenAI({
|
||||
apiKey: selectedProvider.apiKey,
|
||||
})
|
||||
|
||||
// Replace placeholders with actual values
|
||||
const promptTemplate = `${demand.arbitrationPrompt}`
|
||||
.replace(/\{\{demand\}\}/g, demand.demand)
|
||||
.replace(/\{\{obligation\}\}/g, obligation);
|
||||
|
||||
|
||||
const { text } = await generateText({
|
||||
model: openai(demand.arbitrationModel),
|
||||
system: "You are an arbitrator that always tells the truth. You must respond with only 'true' or 'false' - no other words or explanations.",
|
||||
prompt: `${promptTemplate}
|
||||
Based on the above information, determine if the fulfillment satisfies the demand.
|
||||
Answer ONLY with 'true' or 'false' - no explanations or additional text.`
|
||||
});
|
||||
|
||||
console.log(`LLM Response: ${text}`);
|
||||
|
||||
const cleanedResponse = text.trim().toLowerCase();
|
||||
return cleanedResponse === 'true';
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error in LLM arbitration:', error);
|
||||
throw new Error(`LLM arbitration failed: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
const addProvider = (provider: LLMProvider): void => {
|
||||
providers.push(provider);
|
||||
};
|
||||
|
||||
const getProvider = (providerName: string): LLMProvider | undefined => {
|
||||
return providers.find(provider => provider.providerName.toLowerCase() === providerName.toLowerCase());
|
||||
};
|
||||
|
||||
return {
|
||||
LLMAbi,
|
||||
arbitrate,
|
||||
encodeDemand,
|
||||
addProvider,
|
||||
getProvider,
|
||||
providers,
|
||||
};
|
||||
};
|
||||
14
index.ts
14
index.ts
|
|
@ -1,12 +1,2 @@
|
|||
import { generateText } from "ai"
|
||||
import { createOpenAI } from "@ai-sdk/openai"
|
||||
|
||||
const openai = createOpenAI({
|
||||
apiKey: undefined,
|
||||
})
|
||||
|
||||
const { text } = await generateText({
|
||||
model: openai("gpt-4.1"),
|
||||
prompt: "What is love?",
|
||||
})
|
||||
console.log(text);
|
||||
export { makeLLMClient } from "./nla";
|
||||
export type { LLMProvider, LLMDemand } from "./nla";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,183 @@
|
|||
/**
|
||||
* Natural Language Agreement (NLA) Client
|
||||
*
|
||||
* Supports multiple LLM providers for arbitration:
|
||||
*
|
||||
* 1. OpenAI:
|
||||
* - providerName: "OpenAI"
|
||||
* - models: "gpt-4", "gpt-4-turbo", "gpt-3.5-turbo", etc.
|
||||
* - Get API key from: https://platform.openai.com/api-keys
|
||||
*
|
||||
* 2. Anthropic (Claude):
|
||||
* - providerName: "Anthropic" or "Claude"
|
||||
* - models: "claude-3-5-sonnet-20241022", "claude-3-opus-20240229", etc.
|
||||
* - Get API key from: https://console.anthropic.com/
|
||||
*
|
||||
* 3. OpenRouter:
|
||||
* - providerName: "OpenRouter"
|
||||
* - models: Any model available on OpenRouter (e.g., "openai/gpt-4", "anthropic/claude-3-opus")
|
||||
* - Get API key from: https://openrouter.ai/keys
|
||||
* - baseURL: "https://openrouter.ai/api/v1" (default)
|
||||
*
|
||||
* Example usage:
|
||||
* ```typescript
|
||||
* const llmClient = makeLLMClient([]);
|
||||
*
|
||||
* // Add OpenAI provider
|
||||
* llmClient.addProvider({
|
||||
* providerName: "OpenAI",
|
||||
* apiKey: "sk-..."
|
||||
* });
|
||||
*
|
||||
* // Add Anthropic provider
|
||||
* llmClient.addProvider({
|
||||
* providerName: "Anthropic",
|
||||
* apiKey: "sk-ant-..."
|
||||
* });
|
||||
*
|
||||
* // Add OpenRouter provider
|
||||
* llmClient.addProvider({
|
||||
* providerName: "OpenRouter",
|
||||
* apiKey: "sk-or-...",
|
||||
* baseURL: "https://openrouter.ai/api/v1"
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
|
||||
import {
|
||||
decodeAbiParameters,
|
||||
encodeAbiParameters,
|
||||
parseAbiParameters,
|
||||
} from "viem";
|
||||
import { generateText } from "ai";
|
||||
import { createOpenAI } from "@ai-sdk/openai";
|
||||
import { createAnthropic } from "@ai-sdk/anthropic";
|
||||
|
||||
|
||||
export type LLMProvider = {
|
||||
providerName: string;
|
||||
apiKey?: string;
|
||||
baseURL?: string; // For OpenRouter or custom endpoints
|
||||
};
|
||||
|
||||
export type LLMDemand = {
|
||||
arbitrationProvider: string;
|
||||
arbitrationModel: string;
|
||||
arbitrationPrompt: string;
|
||||
demand: string;
|
||||
};
|
||||
|
||||
export const makeLLMClient = (
|
||||
providers: LLMProvider[],
|
||||
) => {
|
||||
|
||||
const LLMAbi = parseAbiParameters(
|
||||
"(string arbitrationProvider, string arbitrationModel, string arbitrationPrompt, string demand)",
|
||||
);
|
||||
const encodeDemand = (demand: LLMDemand) => {
|
||||
return encodeAbiParameters(
|
||||
LLMAbi,
|
||||
[demand],
|
||||
);
|
||||
};
|
||||
|
||||
const arbitrate = async (demand: LLMDemand, obligation: string): Promise<boolean> => {
|
||||
try {
|
||||
const matchingProvider = providers.find(provider =>
|
||||
provider.providerName.toLowerCase().includes(demand.arbitrationProvider.toLowerCase()) ||
|
||||
demand.arbitrationProvider.toLowerCase().includes(provider.providerName.toLowerCase())
|
||||
);
|
||||
|
||||
const selectedProvider = matchingProvider || providers[0];
|
||||
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No LLM provider available');
|
||||
}
|
||||
|
||||
console.log(`Using provider: ${selectedProvider.providerName} for arbitration demand: ${JSON.stringify(demand)}`);
|
||||
|
||||
// Replace placeholders with actual values
|
||||
const promptTemplate = `${demand.arbitrationPrompt}`
|
||||
.replace(/\{\{demand\}\}/g, demand.demand)
|
||||
.replace(/\{\{obligation\}\}/g, obligation);
|
||||
|
||||
const systemPrompt = "You are an arbitrator that always tells the truth. You must respond with only 'true' or 'false' - no other words or explanations.";
|
||||
const userPrompt = `${promptTemplate}
|
||||
Based on the above information, determine if the fulfillment satisfies the demand.
|
||||
Answer ONLY with 'true' or 'false' - no explanations or additional text.`;
|
||||
|
||||
let text: string;
|
||||
const providerName = selectedProvider.providerName.toLowerCase();
|
||||
|
||||
if (providerName === 'openai' || providerName.includes('openai')) {
|
||||
const openai = createOpenAI({
|
||||
apiKey: selectedProvider.apiKey,
|
||||
baseURL: selectedProvider.baseURL,
|
||||
});
|
||||
|
||||
const result = await generateText({
|
||||
model: openai(demand.arbitrationModel),
|
||||
system: systemPrompt,
|
||||
prompt: userPrompt,
|
||||
});
|
||||
text = result.text;
|
||||
|
||||
} else if (providerName === 'anthropic' || providerName.includes('anthropic') || providerName.includes('claude')) {
|
||||
const anthropic = createAnthropic({
|
||||
apiKey: selectedProvider.apiKey,
|
||||
baseURL: selectedProvider.baseURL,
|
||||
});
|
||||
|
||||
const result = await generateText({
|
||||
model: anthropic(demand.arbitrationModel),
|
||||
system: systemPrompt,
|
||||
prompt: userPrompt,
|
||||
});
|
||||
text = result.text;
|
||||
|
||||
} else if (providerName === 'openrouter' || providerName.includes('openrouter')) {
|
||||
// OpenRouter uses OpenAI-compatible API
|
||||
const openrouter = createOpenAI({
|
||||
apiKey: selectedProvider.apiKey,
|
||||
baseURL: selectedProvider.baseURL,
|
||||
});
|
||||
|
||||
const result = await generateText({
|
||||
model: openrouter(demand.arbitrationModel),
|
||||
system: systemPrompt,
|
||||
prompt: userPrompt,
|
||||
});
|
||||
text = result.text;
|
||||
|
||||
} else {
|
||||
throw new Error(`Unsupported provider: ${selectedProvider.providerName}`);
|
||||
}
|
||||
|
||||
console.log(`LLM Response: ${text}`);
|
||||
|
||||
const cleanedResponse = text.trim().toLowerCase();
|
||||
return cleanedResponse === 'true';
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in LLM arbitration:', error);
|
||||
throw new Error(`LLM arbitration failed: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
const addProvider = (provider: LLMProvider): void => {
|
||||
providers.push(provider);
|
||||
};
|
||||
|
||||
const getProvider = (providerName: string): LLMProvider | undefined => {
|
||||
return providers.find(provider => provider.providerName.toLowerCase() === providerName.toLowerCase());
|
||||
};
|
||||
|
||||
return {
|
||||
LLMAbi,
|
||||
arbitrate,
|
||||
encodeDemand,
|
||||
addProvider,
|
||||
getProvider,
|
||||
providers,
|
||||
};
|
||||
};
|
||||
|
|
@ -25,10 +25,11 @@
|
|||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^2.0.50",
|
||||
"@ai-sdk/anthropic": "^3.0.2",
|
||||
"@ai-sdk/openai": "^3.0.2",
|
||||
"@viem/anvil": "^0.0.10",
|
||||
"ai": "^5.0.68",
|
||||
"alkahest-ts": "git+https://github.com/VAR-META-Tech/alkahest.git#ts-package",
|
||||
"ai": "^6.0.5",
|
||||
"alkahest-ts": "github:arkhai-io/alkahest",
|
||||
"arktype": "^2.1.23",
|
||||
"viem": "^2.42.1",
|
||||
"zod": "^3.25.76"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { afterAll, beforeAll, beforeEach, expect, test } from "bun:test";
|
||||
import { makeLLMClient } from "../clients/nla";
|
||||
import type { LLMProvider, LLMDemand } from "../clients/nla";
|
||||
import { makeLLMClient } from "..";
|
||||
import type { LLMProvider, LLMDemand } from "..";
|
||||
import {
|
||||
setupTestEnvironment,
|
||||
type TestContext,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
setupTestEnvironment,
|
||||
type TestContext,
|
||||
} from "alkahest-ts";
|
||||
import { makeLLMClient } from "../clients/nla";
|
||||
import { makeLLMClient } from "..";
|
||||
|
||||
let testContext: TestContext;
|
||||
let charlieClient: ReturnType<typeof testContext.charlie.client.extend<{ llm: ReturnType<typeof makeLLMClient> }>>;
|
||||
|
|
@ -40,7 +40,7 @@ afterAll(async () => {
|
|||
test("listenAndArbitrate Natural Language", async () => {
|
||||
|
||||
const arbiter = testContext.addresses.trustedOracleArbiter;
|
||||
const demand = testContext.alice.client.arbiters.general.trustedOracle.encode({
|
||||
const demand = testContext.alice.client.arbiters.general.trustedOracle.encodeDemand({
|
||||
oracle: testContext.bob.address,
|
||||
data: charlieClient.llm.encodeDemand({
|
||||
arbitrationProvider: "OpenAI",
|
||||
|
|
@ -55,7 +55,7 @@ Fulfillment: {{obligation}}`,
|
|||
});
|
||||
|
||||
const { attested: escrow } =
|
||||
await testContext.alice.client.erc20.permitAndBuyWithErc20(
|
||||
await testContext.alice.client.erc20.escrow.nonTierable.permitAndCreate(
|
||||
{
|
||||
address: testContext.mockAddresses.erc20A,
|
||||
value: 10n,
|
||||
|
|
@ -66,7 +66,7 @@ Fulfillment: {{obligation}}`,
|
|||
|
||||
const obligationAbi = parseAbiParameters("(string item)");
|
||||
const { decisions, unwatch } =
|
||||
await testContext.bob.client.oracle.listenAndArbitrate(
|
||||
await testContext.bob.client.arbiters.general.trustedOracle.listenAndArbitrate(
|
||||
async (attestation) => {
|
||||
console.log("arbitrating");
|
||||
const obligation = testContext.bob.client.extractObligationData(
|
||||
|
|
@ -102,15 +102,16 @@ Fulfillment: {{obligation}}`,
|
|||
escrow.uid,
|
||||
);
|
||||
|
||||
await testContext.bob.client.oracle.requestArbitration(
|
||||
await testContext.bob.client.arbiters.general.trustedOracle.requestArbitration(
|
||||
fulfillment.uid,
|
||||
testContext.bob.address,
|
||||
demand
|
||||
);
|
||||
|
||||
//Should call WaitForArbitration()
|
||||
await Bun.sleep(2000);
|
||||
await Bun.sleep(5000);
|
||||
|
||||
const collectionHash = await testContext.bob.client.erc20.collectEscrow(
|
||||
const collectionHash = await testContext.bob.client.erc20.escrow.nonTierable.collect(
|
||||
escrow.uid,
|
||||
fulfillment.uid,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue