From f42408c825b71ff18374d78d6b1a75662b630cd7 Mon Sep 17 00:00:00 2001 From: ngoc Date: Fri, 2 Jan 2026 16:30:17 +0700 Subject: [PATCH] Update to new version ts sdk, update readme --- .gitignore | 3 + .vscode/settings.json | 5 ++ README.md | 156 ++++++++++++++++++++++++++--------- bun.lock | 4 +- cli/client/collect-escrow.ts | 2 +- cli/client/create-escrow.ts | 4 +- cli/client/fulfill-escrow.ts | 8 +- cli/index.ts | 8 +- cli/server/deploy.ts | 25 +++--- cli/server/oracle.ts | 2 +- package.json | 2 +- tests/nlaOracle.test.ts | 11 +-- 12 files changed, 162 insertions(+), 68 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 8d6c402..ac666dd 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ anvil.log .DS_Store .env + +# Local deployment files +cli/deployments/localhost.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..58f6305 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "Tierable" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 536f55f..dadc439 100644 --- a/README.md +++ b/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 \ + --oracle \ + --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 diff --git a/bun.lock b/bun.lock index 441adc3..5152949 100644 --- a/bun.lock +++ b/bun.lock @@ -9,8 +9,8 @@ "@ai-sdk/openai": "^3.0.2", "@viem/anvil": "^0.0.10", "ai": "^6.0.5", - "alkahest-ts": "git+https://github.com/VAR-META-Tech/alkahest.git#ts-package", "arktype": "^2.1.23", + "alkahest-ts": "github:arkhai-io/alkahest", "viem": "^2.42.1", "zod": "^3.25.76", }, @@ -67,7 +67,7 @@ "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=="], diff --git a/cli/client/collect-escrow.ts b/cli/client/collect-escrow.ts index f144dd0..3746fa3 100644 --- a/cli/client/collect-escrow.ts +++ b/cli/client/collect-escrow.ts @@ -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}`, ); diff --git a/cli/client/create-escrow.ts b/cli/client/create-escrow.ts index a99510e..0ac0731 100644 --- a/cli/client/create-escrow.ts +++ b/cli/client/create-escrow.ts @@ -207,7 +207,7 @@ Fulfillment: {{obligation}}`; // 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, @@ -218,7 +218,7 @@ Fulfillment: {{obligation}}`; }); // 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), diff --git a/cli/client/fulfill-escrow.ts b/cli/client/fulfill-escrow.ts index d6cf1d2..9a72570 100644 --- a/cli/client/fulfill-escrow.ts +++ b/cli/client/fulfill-escrow.ts @@ -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,18 +162,19 @@ 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"); diff --git a/cli/index.ts b/cli/index.ts index 3fb3c4d..e50beff 100755 --- a/cli/index.ts +++ b/cli/index.ts @@ -267,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; @@ -298,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, }); @@ -319,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; @@ -336,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; diff --git a/cli/server/deploy.ts b/cli/server/deploy.ts index 9e99c5d..092adb5 100644 --- a/cli/server/deploy.ts +++ b/cli/server/deploy.ts @@ -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) ] ); diff --git a/cli/server/oracle.ts b/cli/server/oracle.ts index 585b59a..3ab3d59 100644 --- a/cli/server/oracle.ts +++ b/cli/server/oracle.ts @@ -170,7 +170,7 @@ async function main() { 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}`); diff --git a/package.json b/package.json index 9fe223a..fd33227 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@ai-sdk/openai": "^3.0.2", "@viem/anvil": "^0.0.10", "ai": "^6.0.5", - "alkahest-ts": "git+https://github.com/VAR-META-Tech/alkahest.git#ts-package", + "alkahest-ts": "github:arkhai-io/alkahest", "arktype": "^2.1.23", "viem": "^2.42.1", "zod": "^3.25.76" diff --git a/tests/nlaOracle.test.ts b/tests/nlaOracle.test.ts index f971c93..7aaf051 100644 --- a/tests/nlaOracle.test.ts +++ b/tests/nlaOracle.test.ts @@ -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(5000); - const collectionHash = await testContext.bob.client.erc20.collectEscrow( + const collectionHash = await testContext.bob.client.erc20.escrow.nonTierable.collect( escrow.uid, fulfillment.uid, );