feat: migrate from StringObligation to CommitRevealObligation, rename devnet to anvil

- Update alkahest-ts SDK to 0.7.2
- Replace StringObligation with CommitRevealObligation across deploy, fulfill, and oracle
- Implement full commit-reveal flow: commit (with bond) -> wait block -> reveal -> reclaim bond
- Use keccak256("{item:string}") as schema for obligation payload
- Add end-to-end test covering alice escrow -> bob fulfill -> charlie arbitrate -> bob collect
- Rename devnet environment to anvil throughout CLI, configs, and docs
- Bump version to 1.1.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
疒奀 2026-02-12 23:04:30 +08:00
parent a18648e8d2
commit 136efdbcff
16 changed files with 252 additions and 99 deletions

2
.gitignore vendored
View File

@ -41,4 +41,4 @@ anvil.log
.env .env
# Local deployment files # Local deployment files
cli/deployments/devnet.json cli/deployments/anvil.json

View File

@ -136,7 +136,7 @@ nla stop
| `nla switch <env>` | Switch between environments | | `nla switch <env>` | Switch between environments |
| `nla network` | Show current environment | | `nla network` | Show current environment |
Available environments: `devnet`, `sepolia`, `base-sepolia`, `mainnet` Available environments: `anvil`, `sepolia`, `base-sepolia`, `mainnet`
### Global Options ### Global Options
@ -445,7 +445,7 @@ nla network
nla switch mainnet nla switch mainnet
# Back to local development # Back to local development
nla switch devnet nla switch anvil
``` ```
## Troubleshooting ## Troubleshooting
@ -512,7 +512,7 @@ natural-language-agreements/
│ │ ├── switch.ts # Switch environments │ │ ├── switch.ts # Switch environments
│ │ └── wallet.ts # Wallet management │ │ └── wallet.ts # Wallet management
│ └── deployments/ # Deployment addresses (generated) │ └── deployments/ # Deployment addresses (generated)
│ ├── devnet.json │ ├── anvil.json
│ ├── sepolia.json │ ├── sepolia.json
│ ├── base-sepolia.json │ ├── base-sepolia.json
│ └── mainnet.json │ └── mainnet.json

View File

@ -3,7 +3,7 @@
"configVersion": 1, "configVersion": 1,
"workspaces": { "workspaces": {
"": { "": {
"name": "natural-language-agreement-extension", "name": "nla",
"dependencies": { "dependencies": {
"@ai-sdk/anthropic": "^3.0.2", "@ai-sdk/anthropic": "^3.0.2",
"@ai-sdk/openai": "^3.0.2", "@ai-sdk/openai": "^3.0.2",
@ -11,7 +11,7 @@
"@perplexity-ai/ai-sdk": "^0.1.2", "@perplexity-ai/ai-sdk": "^0.1.2",
"@viem/anvil": "^0.0.10", "@viem/anvil": "^0.0.10",
"ai": "^6.0.5", "ai": "^6.0.5",
"alkahest-ts": "^0.6.1", "alkahest-ts": "0.7.2",
"arktype": "^2.1.23", "arktype": "^2.1.23",
"viem": "^2.42.1", "viem": "^2.42.1",
"zod": "^3.25.76", "zod": "^3.25.76",
@ -29,15 +29,15 @@
"packages": { "packages": {
"@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="], "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="],
"@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/anthropic": ["@ai-sdk/anthropic@3.0.42", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.14" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-snoLXB9DmvAmmngbPN/Io8IGzZ9zWpC208EgIIztYf1e1JhwuMkgKCYkL30vGhSen4PrBafu2+sO4G/17wu45A=="],
"@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/gateway": ["@ai-sdk/gateway@3.0.42", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.14", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Il9lZWPUQMX59H5yJvA08gxfL2Py8oHwvAYRnK0Mt91S+JgPcyk/yEmXNDZG9ghJrwSawtK5Yocy8OnzsTOGsw=="],
"@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/openai": ["@ai-sdk/openai@3.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.14" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-pLMxWOypwroXiK9dxNpn60/HGhWWWDEOJ3lo9vZLoxvpJNtKnLKojwVIvlW3yEjlD7ll1+jUO2uzsABNTaP5Yg=="],
"@ai-sdk/provider": ["@ai-sdk/provider@3.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-2lR4w7mr9XrydzxBSjir4N6YMGdXD+Np1Sh0RXABh7tWdNFFwIeRI1Q+SaYZMbfL8Pg8RRLcrxQm51yxTLhokg=="], "@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
"@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=="], "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.14", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7bzKd9lgiDeXM7O4U4nQ8iTxguAOkg8LZGD9AfDVZYjO5cKYRwBPwVjboFcVrxncRHu0tYxZtXZtiLKpG4pEng=="],
"@ark/schema": ["@ark/schema@0.56.0", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA=="], "@ark/schema": ["@ark/schema@0.56.0", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA=="],
@ -65,25 +65,25 @@
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@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=="], "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
"@types/node": ["@types/node@20.19.30", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g=="], "@types/node": ["@types/node@20.19.33", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw=="],
"@vercel/oidc": ["@vercel/oidc@3.0.5", "", {}, "sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw=="], "@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
"@viem/anvil": ["@viem/anvil@0.0.10", "", { "dependencies": { "execa": "^7.1.1", "get-port": "^6.1.2", "http-proxy": "^1.18.1", "ws": "^8.13.0" } }, "sha512-9PzYXBRikfSUhhm8Bd0avv07agwcbMJ5FaSu2D2vbE0cxkvXGtolL3fW5nz2yefMqOqVQL4XzfM5nwY81x3ytw=="], "@viem/anvil": ["@viem/anvil@0.0.10", "", { "dependencies": { "execa": "^7.1.1", "get-port": "^6.1.2", "http-proxy": "^1.18.1", "ws": "^8.13.0" } }, "sha512-9PzYXBRikfSUhhm8Bd0avv07agwcbMJ5FaSu2D2vbE0cxkvXGtolL3fW5nz2yefMqOqVQL4XzfM5nwY81x3ytw=="],
"abitype": ["abitype@1.1.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A=="], "abitype": ["abitype@1.2.3", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg=="],
"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=="], "ai": ["ai@6.0.82", "", { "dependencies": { "@ai-sdk/gateway": "3.0.42", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.14", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-WLml1ab2IXtREgkxrq2Pl6lFO6NKgC17MqTzmK5mO1UO6tMAJiVjkednw9p0j4+/LaUIZQoRiIT8wA37LswZ9Q=="],
"alkahest-ts": ["alkahest-ts@0.6.1", "", { "dependencies": { "@viem/anvil": "^0.0.10", "arktype": "^2.1.23", "zod": "^3.25.76" }, "peerDependencies": { "typescript": "^5.9.3", "viem": "^2.38.3" } }, "sha512-0u1xUM9OLca6emKDVzn6ISx4VFg7TGZtA/7hnT3SOHOWVPLk5ruX12tbX5MO7hG1jxVHAO3wgIE2t7vl864/nQ=="], "alkahest-ts": ["alkahest-ts@0.7.2", "", { "dependencies": { "@viem/anvil": "^0.0.10", "arktype": "^2.1.23", "zod": "^3.25.76" }, "peerDependencies": { "typescript": "^5.9.3", "viem": "^2.38.3" } }, "sha512-TWk1JftpPAQDSfTymzKxkeiew2/VxIMvbsgLkPm3ECsloxZe4FNUIAXOKQH3PEqIHeKboBdtBN5TrsL0WhbooQ=="],
"arkregex": ["arkregex@0.0.5", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw=="], "arkregex": ["arkregex@0.0.5", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw=="],
"arktype": ["arktype@2.1.29", "", { "dependencies": { "@ark/schema": "0.56.0", "@ark/util": "0.56.0", "arkregex": "0.0.5" } }, "sha512-jyfKk4xIOzvYNayqnD8ZJQqOwcrTOUbIU4293yrzAjA3O1dWh61j71ArMQ6tS/u4pD7vabSPe7nG3RCyoXW6RQ=="], "arktype": ["arktype@2.1.29", "", { "dependencies": { "@ark/schema": "0.56.0", "@ark/util": "0.56.0", "arkregex": "0.0.5" } }, "sha512-jyfKk4xIOzvYNayqnD8ZJQqOwcrTOUbIU4293yrzAjA3O1dWh61j71ArMQ6tS/u4pD7vabSPe7nG3RCyoXW6RQ=="],
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="], "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
@ -119,7 +119,7 @@
"onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="], "onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="],
"ox": ["ox@0.9.6", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.0.9", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg=="], "ox": ["ox@0.12.1", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-uU0llpthaaw4UJoXlseCyBHmQ3bLrQmz9rRLIAUHqv46uHuae9SE+ukYBRIPVCnlEnHKuWjDUcDFHWx9gbGNoA=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
@ -137,20 +137,18 @@
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"viem": ["viem@2.42.1", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.1.0", "isows": "1.0.7", "ox": "0.9.6", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-NzT/f54jT+b0Um6pYzN/uAGMLg+3twhricAzXS+XH8pVIREzPEh7P25rlhPQnLYiPWzQd9mrFcvnm73Sc8bx+A=="], "viem": ["viem@2.45.3", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.12.1", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-axOD7rIbGiDHHA1MHKmpqqTz3CMCw7YpE/FVypddQMXL5i364VkNZh9JeEJH17NO484LaZUOMueo35IXyL76Mw=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"bun-types/@types/node": ["@types/node@25.0.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA=="],
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
"ox/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], "ox/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
"bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "viem/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
} }
} }

View File

@ -99,7 +99,7 @@ Monitor the escrow and arbitration progress:
```bash ```bash
nla escrow:status \ nla escrow:status \
--escrow-uid 0x... \ --escrow-uid 0x... \
--deployment ./cli/deployments/devnet.json --deployment ./cli/deployments/anvil.json
``` ```
This will show: This will show:
@ -185,7 +185,7 @@ Options:
--token <address> ERC20 token address (required) --token <address> ERC20 token address (required)
--oracle <address> Oracle address (required) --oracle <address> Oracle address (required)
--private-key <key> Your private key (or set PRIVATE_KEY env var) --private-key <key> Your private key (or set PRIVATE_KEY env var)
--deployment <path> Deployment file (default: ./cli/deployments/devnet.json) --deployment <path> Deployment file (default: ./cli/deployments/anvil.json)
--rpc-url <url> RPC URL (default: from deployment) --rpc-url <url> RPC URL (default: from deployment)
--help, -h Show help --help, -h Show help
``` ```
@ -200,7 +200,7 @@ Options:
--fulfillment <text> Your fulfillment text (required) --fulfillment <text> Your fulfillment text (required)
--oracle <address> Oracle address (required) --oracle <address> Oracle address (required)
--private-key <key> Your private key (or set PRIVATE_KEY env var) --private-key <key> Your private key (or set PRIVATE_KEY env var)
--deployment <path> Deployment file (default: ./cli/deployments/devnet.json) --deployment <path> Deployment file (default: ./cli/deployments/anvil.json)
--rpc-url <url> RPC URL (default: from deployment) --rpc-url <url> RPC URL (default: from deployment)
--help, -h Show help --help, -h Show help
``` ```
@ -214,7 +214,7 @@ Options:
--escrow-uid <uid> Escrow UID (required) --escrow-uid <uid> Escrow UID (required)
--fulfillment-uid <uid> Approved fulfillment UID (required) --fulfillment-uid <uid> Approved fulfillment UID (required)
--private-key <key> Your private key (or set PRIVATE_KEY env var) --private-key <key> Your private key (or set PRIVATE_KEY env var)
--deployment <path> Deployment file (default: ./cli/deployments/devnet.json) --deployment <path> Deployment file (default: ./cli/deployments/anvil.json)
--rpc-url <url> RPC URL (default: from deployment) --rpc-url <url> RPC URL (default: from deployment)
--help, -h Show help --help, -h Show help
``` ```
@ -226,7 +226,7 @@ nla escrow:status [options]
Options: Options:
--escrow-uid <uid> Escrow UID to check (required) --escrow-uid <uid> Escrow UID to check (required)
--deployment <path> Deployment file (default: ./cli/deployments/devnet.json) --deployment <path> Deployment file (default: ./cli/deployments/anvil.json)
--rpc-url <url> RPC URL (default: from deployment) --rpc-url <url> RPC URL (default: from deployment)
--help, -h Show help --help, -h Show help
``` ```
@ -293,7 +293,7 @@ nla escrow:fulfill \
# Terminal 2: Check the status # Terminal 2: Check the status
nla escrow:status \ nla escrow:status \
--escrow-uid 0xd9e1402e96c2f7a64e60bf53a45445f7254e9b72389f6ede25181bff542d7b65 \ --escrow-uid 0xd9e1402e96c2f7a64e60bf53a45445f7254e9b72389f6ede25181bff542d7b65 \
--deployment ./cli/deployments/devnet.json --deployment ./cli/deployments/anvil.json
# Terminal 2: Collect the escrow (as Bob) # Terminal 2: Collect the escrow (as Bob)
nla escrow:collect \ nla escrow:collect \

View File

@ -32,7 +32,7 @@ Options:
--escrow-uid <uid> Escrow UID to collect (required) --escrow-uid <uid> Escrow UID to collect (required)
--fulfillment-uid <uid> Fulfillment UID that was approved (required) --fulfillment-uid <uid> Fulfillment UID that was approved (required)
--private-key <key> Your private key (required) --private-key <key> Your private key (required)
--deployment <path> Path to deployment file (default: ./cli/deployments/devnet.json) --deployment <path> Path to deployment file (default: ./cli/deployments/anvil.json)
--rpc-url <url> RPC URL (default: from deployment file) --rpc-url <url> RPC URL (default: from deployment file)
--help, -h Display this help message --help, -h Display this help message

View File

@ -7,7 +7,7 @@
*/ */
import { parseArgs } from "util"; import { parseArgs } from "util";
import { createWalletClient, http, publicActions, formatEther } from "viem"; import { createWalletClient, http, publicActions, formatEther, toHex, keccak256 } from "viem";
import { privateKeyToAccount } from "viem/accounts"; import { privateKeyToAccount } from "viem/accounts";
import { existsSync, readFileSync } from "fs"; import { existsSync, readFileSync } from "fs";
import { resolve, dirname, join } from "path"; import { resolve, dirname, join } from "path";
@ -35,7 +35,7 @@ Options:
--fulfillment <text> Your fulfillment text (required) --fulfillment <text> Your fulfillment text (required)
--oracle <address> Oracle address that will arbitrate (required) --oracle <address> Oracle address that will arbitrate (required)
--private-key <key> Your private key (required) --private-key <key> Your private key (required)
--deployment <path> Path to deployment file (default: ./cli/deployments/devnet.json) --deployment <path> Path to deployment file (default: ./cli/deployments/anvil.json)
--rpc-url <url> RPC URL (default: from deployment file) --rpc-url <url> RPC URL (default: from deployment file)
--help, -h Display this help message --help, -h Display this help message
@ -170,11 +170,40 @@ async function main() {
console.log("📋 Creating fulfillment obligation...\n"); console.log("📋 Creating fulfillment obligation...\n");
// Create the fulfillment // Create the fulfillment using CommitRevealObligation (commit-reveal flow)
const { attested: fulfillmentAttestation } = await client.stringObligation.doObligation( const schema = keccak256(toHex("{item:string}"));
fulfillment, const salt = keccak256(toHex(crypto.randomUUID()));
const payload = toHex(fulfillment);
const obligationData = { payload, salt, schema };
// Step 1: Compute and submit commitment
console.log("🔒 Computing commitment...");
const commitment = await client.commitReveal.computeCommitment(
escrowUid as `0x${string}`,
account.address,
obligationData,
);
console.log(` Commitment: ${commitment}`);
console.log("📝 Submitting commitment (with bond)...");
const { hash: commitHash } = await client.commitReveal.commit(commitment);
console.log(` Commit tx: ${commitHash}`);
// Step 2: Wait for next block
console.log("⏳ Waiting for next block...");
await walletClient.waitForTransactionReceipt({ hash: commitHash });
// Step 3: Reveal - create the obligation
console.log("🔓 Revealing obligation...");
const { attested: fulfillmentAttestation } = await client.commitReveal.doObligation(
obligationData,
escrowUid as `0x${string}`, escrowUid as `0x${string}`,
); );
// Step 4: Reclaim bond
console.log("💰 Reclaiming bond...");
await client.commitReveal.reclaimBond(fulfillmentAttestation.uid);
console.log("✅ Fulfillment created!\n"); console.log("✅ Fulfillment created!\n");
console.log("📋 Fulfillment Details:"); console.log("📋 Fulfillment Details:");
console.log(` UID: ${fulfillmentAttestation.uid}`); console.log(` UID: ${fulfillmentAttestation.uid}`);

View File

@ -72,14 +72,14 @@ export async function runDevCommand(cliDir: string, envPath?: string, cliPrivate
console.log(`${colors.blue} Natural Language Agreement Oracle - Quick Setup${colors.reset}`); console.log(`${colors.blue} Natural Language Agreement Oracle - Quick Setup${colors.reset}`);
console.log(`${colors.blue}════════════════════════════════════════════════════════${colors.reset}\n`); console.log(`${colors.blue}════════════════════════════════════════════════════════${colors.reset}\n`);
// Auto-switch to devnet environment // Auto-switch to anvil environment
const currentEnv = getCurrentEnvironment(); const currentEnv = getCurrentEnvironment();
if (currentEnv !== 'devnet') { if (currentEnv !== 'anvil') {
console.log(`${colors.yellow}🔄 Switching environment from ${currentEnv} to devnet...${colors.reset}`); console.log(`${colors.yellow}🔄 Switching environment from ${currentEnv} to anvil...${colors.reset}`);
setCurrentEnvironment('devnet'); setCurrentEnvironment('anvil');
console.log(`${colors.green}✅ Switched to devnet${colors.reset}\n`); console.log(`${colors.green}✅ Switched to anvil${colors.reset}\n`);
} else { } else {
console.log(`${colors.green}✅ Already on devnet environment${colors.reset}\n`); console.log(`${colors.green}✅ Already on anvil environment${colors.reset}\n`);
} }
// Load .env file first // Load .env file first
@ -188,8 +188,8 @@ export async function runDevCommand(cliDir: string, envPath?: string, cliPrivate
const oracleScript = join(cliDir, 'server', 'oracle.js'); const oracleScript = join(cliDir, 'server', 'oracle.js');
// Look for deployment file in source directory (for local dev) or dist directory (for installed package) // Look for deployment file in source directory (for local dev) or dist directory (for installed package)
const sourcePath = join(process.cwd(), 'cli', 'deployments', 'devnet.json'); const sourcePath = join(process.cwd(), 'cli', 'deployments', 'anvil.json');
const distPath = join(cliDir, 'deployments', 'devnet.json'); const distPath = join(cliDir, 'deployments', 'anvil.json');
const deploymentFile = existsSync(sourcePath) ? sourcePath : distPath; const deploymentFile = existsSync(sourcePath) ? sourcePath : distPath;
const oracleArgs = ['run', oracleScript, '--deployment', deploymentFile]; const oracleArgs = ['run', oracleScript, '--deployment', deploymentFile];

View File

@ -33,7 +33,7 @@ export function runSwitchCommand(env?: string) {
const current = getCurrentEnvironment(); const current = getCurrentEnvironment();
console.log(`${colors.blue}Current environment:${colors.reset} ${colors.green}${current}${colors.reset}\n`); console.log(`${colors.blue}Current environment:${colors.reset} ${colors.green}${current}${colors.reset}\n`);
console.log('Available environments:'); console.log('Available environments:');
console.log(' • devnet (local Anvil blockchain)'); console.log(' • anvil (local Anvil blockchain)');
console.log(' • sepolia (Ethereum Sepolia testnet)'); console.log(' • sepolia (Ethereum Sepolia testnet)');
console.log(' • base-sepolia (Base Sepolia testnet)'); console.log(' • base-sepolia (Base Sepolia testnet)');
console.log(' • mainnet (Ethereum mainnet)\n'); console.log(' • mainnet (Ethereum mainnet)\n');
@ -43,10 +43,10 @@ export function runSwitchCommand(env?: string) {
} }
// Validate environment // Validate environment
const validEnvs = ['devnet', 'sepolia', 'base-sepolia', 'mainnet']; const validEnvs = ['anvil', 'sepolia', 'base-sepolia', 'mainnet'];
if (!validEnvs.includes(env)) { if (!validEnvs.includes(env)) {
console.error(`${colors.red}❌ Invalid environment: ${env}${colors.reset}`); console.error(`${colors.red}❌ Invalid environment: ${env}${colors.reset}`);
console.log('Valid environments: devnet, sepolia, base-sepolia, mainnet\n'); console.log('Valid environments: anvil, sepolia, base-sepolia, mainnet\n');
process.exit(1); process.exit(1);
} }
@ -62,7 +62,7 @@ export function runSwitchCommand(env?: string) {
console.log(`${colors.green}✅ Switched to ${env}${colors.reset}\n`); console.log(`${colors.green}✅ Switched to ${env}${colors.reset}\n`);
// Show info about the environment // Show info about the environment
if (env === 'devnet') { if (env === 'anvil') {
console.log('📝 Using local Anvil blockchain (http://localhost:8545)'); console.log('📝 Using local Anvil blockchain (http://localhost:8545)');
console.log(' Run "nla dev" to start the development environment\n'); console.log(' Run "nla dev" to start the development environment\n');
} else if (env === 'sepolia') { } else if (env === 'sepolia') {

View File

@ -20,7 +20,7 @@
"attestationEscrowObligation": "", "attestationEscrowObligation": "",
"attestationEscrowObligation2": "", "attestationEscrowObligation2": "",
"attestationBarterUtils": "", "attestationBarterUtils": "",
"stringObligation": "", "commitRevealObligation": "",
"trivialArbiter": "", "trivialArbiter": "",
"trustedOracleArbiter": "", "trustedOracleArbiter": "",
"anyArbiter": "", "anyArbiter": "",

View File

@ -20,7 +20,7 @@
"attestationEscrowObligation": "", "attestationEscrowObligation": "",
"attestationEscrowObligation2": "", "attestationEscrowObligation2": "",
"attestationBarterUtils": "", "attestationBarterUtils": "",
"stringObligation": "", "commitRevealObligation": "",
"trivialArbiter": "", "trivialArbiter": "",
"trustedOracleArbiter": "", "trustedOracleArbiter": "",
"anyArbiter": "", "anyArbiter": "",

View File

@ -30,7 +30,7 @@ Commands:
deploy Deploy contracts to blockchain deploy Deploy contracts to blockchain
start-oracle Start the oracle service start-oracle Start the oracle service
stop Stop all services (Anvil + Oracle) stop Stop all services (Anvil + Oracle)
switch [env] Switch between environments (devnet, sepolia, base-sepolia, mainnet) switch [env] Switch between environments (anvil, sepolia, base-sepolia, mainnet)
network Show current network/environment network Show current network/environment
wallet:set Set wallet private key wallet:set Set wallet private key
wallet:show Show current wallet address wallet:show Show current wallet address

View File

@ -164,7 +164,7 @@ async function main() {
const SchemaRegistry = fixtures.SchemaRegistry; const SchemaRegistry = fixtures.SchemaRegistry;
const MockERC20Permit = fixtures.MockERC20Permit; const MockERC20Permit = fixtures.MockERC20Permit;
const TrustedOracleArbiter = contracts.TrustedOracleArbiter; const TrustedOracleArbiter = contracts.TrustedOracleArbiter;
const StringObligation = contracts.StringObligation; const CommitRevealObligation = contracts.CommitRevealObligation;
const ERC20EscrowObligation = contracts.ERC20EscrowObligation; const ERC20EscrowObligation = contracts.ERC20EscrowObligation;
const ERC20PaymentObligation = contracts.ERC20PaymentObligation; const ERC20PaymentObligation = contracts.ERC20PaymentObligation;
const ERC20BarterUtils = contracts.ERC20BarterUtils; const ERC20BarterUtils = contracts.ERC20BarterUtils;
@ -229,10 +229,10 @@ async function main() {
// Deploy obligations // Deploy obligations
console.log("📋 Deploying obligations...\n"); console.log("📋 Deploying obligations...\n");
addresses.stringObligation = await deployContract( addresses.commitRevealObligation = await deployContract(
"String Obligation", "Commit Reveal Obligation",
StringObligation.abi.abi, CommitRevealObligation.abi.abi,
StringObligation.abi.bytecode.object, CommitRevealObligation.abi.bytecode.object,
[addresses.eas, addresses.easSchemaRegistry] [addresses.eas, addresses.easSchemaRegistry]
); );
@ -338,8 +338,8 @@ async function main() {
const scriptDir = import.meta.dir; const scriptDir = import.meta.dir;
const projectRoot = resolve(scriptDir, "../.."); const projectRoot = resolve(scriptDir, "../..");
// Map localhost to devnet for file naming // Map localhost to anvil for file naming
const deploymentFileName = network === "localhost" ? "devnet" : network; const deploymentFileName = network === "localhost" ? "anvil" : network;
const outputPath = args.output || resolve(projectRoot, `cli/deployments/${deploymentFileName}.json`); const outputPath = args.output || resolve(projectRoot, `cli/deployments/${deploymentFileName}.json`);
const outputDir = resolve(outputPath, ".."); const outputDir = resolve(outputPath, "..");

View File

@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
import { parseArgs } from "util"; import { parseArgs } from "util";
import { parseAbiParameters, createWalletClient, http, publicActions } from "viem"; import { createWalletClient, http, publicActions, fromHex } from "viem";
import { privateKeyToAccount } from "viem/accounts"; import { privateKeyToAccount } from "viem/accounts";
import { makeLLMClient } from "../.."; import { makeLLMClient } from "../..";
import { existsSync, readFileSync } from "fs"; import { existsSync, readFileSync } from "fs";
@ -250,22 +250,16 @@ async function main() {
console.log("\n🎯 LLM Arbitrator configured and ready\n"); console.log("\n🎯 LLM Arbitrator configured and ready\n");
console.log("👂 Listening for arbitration requests...\n"); console.log("👂 Listening for arbitration requests...\n");
// Define the obligation ABI
const obligationAbi = parseAbiParameters("(string item)");
// Start listening and arbitrating // Start listening and arbitrating
const { unwatch } = await client.arbiters.general.trustedOracle.arbitrateMany( const { unwatch } = await client.arbiters.general.trustedOracle.arbitrateMany(
async ({ attestation, demand }) => { async ({ attestation, demand }) => {
console.log(`\n📨 New arbitration request received!`); console.log(`\n📨 New arbitration request received!`);
console.log(` Attestation UID: ${attestation.uid}`); console.log(` Attestation UID: ${attestation.uid}`);
try { try {
// Extract obligation data // Extract obligation data from CommitRevealObligation
const obligation = client.extractObligationData( const commitRevealData = client.commitReveal.decode(attestation.data);
obligationAbi, const obligationItem = fromHex(commitRevealData.payload, 'string');
attestation,
);
const obligationItem = obligation[0].item;
console.log(` Obligation: "${obligationItem}"`); console.log(` Obligation: "${obligationItem}"`);
const trustedOracleDemandData = client.arbiters.general.trustedOracle.decodeDemand(demand); const trustedOracleDemandData = client.arbiters.general.trustedOracle.decodeDemand(demand);
@ -324,7 +318,7 @@ async function main() {
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("📝 Next Steps - Create Your First Escrow:\n"); console.log("📝 Next Steps - Create Your First Escrow:\n");
if (currentEnv === 'devnet') { if (currentEnv === 'anvil') {
console.log("1. Export your private key (use a test account):"); console.log("1. Export your private key (use a test account):");
console.log(" export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80\n"); console.log(" export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80\n");
} else { } else {
@ -337,7 +331,7 @@ async function main() {
console.log(" --demand \"The sky is blue\" \\"); console.log(" --demand \"The sky is blue\" \\");
console.log(" --amount 10 \\"); console.log(" --amount 10 \\");
if (currentEnv === 'devnet' && deployment.addresses.mockERC20A) { if (currentEnv === 'anvil' && deployment.addresses.mockERC20A) {
console.log(` --token ${deployment.addresses.mockERC20A} \\`); console.log(` --token ${deployment.addresses.mockERC20A} \\`);
} else { } else {
console.log(" --token <ERC20_TOKEN_ADDRESS> \\"); console.log(" --token <ERC20_TOKEN_ADDRESS> \\");

View File

@ -62,7 +62,7 @@ export function getChainFromNetwork(network: string): Chain {
switch (normalized) { switch (normalized) {
case "localhost": case "localhost":
case "devnet": case "anvil":
return foundry; return foundry;
case "sepolia": case "sepolia":
case "ethereum-sepolia": case "ethereum-sepolia":
@ -95,15 +95,15 @@ export function getCurrentEnvironment(): string {
const configPath = join(getNLAConfigDir(), 'config.json'); const configPath = join(getNLAConfigDir(), 'config.json');
if (!existsSync(configPath)) { if (!existsSync(configPath)) {
// Default to devnet // Default to anvil
return 'devnet'; return 'anvil';
} }
try { try {
const config = JSON.parse(readFileSync(configPath, 'utf-8')); const config = JSON.parse(readFileSync(configPath, 'utf-8'));
return config.environment || 'devnet'; return config.environment || 'anvil';
} catch (e) { } catch (e) {
return 'devnet'; return 'anvil';
} }
} }
@ -177,7 +177,7 @@ export function clearPrivateKey(): void {
/** /**
* Get deployment path for environment * Get deployment path for environment
* Automatically looks in the CLI directory where utils.ts is located * Automatically looks in the CLI directory where utils.ts is located
* @param env - The environment name (devnet, sepolia, etc.) * @param env - The environment name (anvil, sepolia, etc.)
*/ */
export function getDeploymentPath(env?: string): string { export function getDeploymentPath(env?: string): string {
const environment = env || getCurrentEnvironment(); const environment = env || getCurrentEnvironment();

View File

@ -1,6 +1,6 @@
{ {
"name": "nla", "name": "nla",
"version": "1.0.8", "version": "1.1.0",
"description": "Natural Language Agreement Oracle - CLI for creating and managing blockchain agreements using natural language", "description": "Natural Language Agreement Oracle - CLI for creating and managing blockchain agreements using natural language",
"type": "module", "type": "module",
"private": false, "private": false,
@ -66,7 +66,7 @@
"@perplexity-ai/ai-sdk": "^0.1.2", "@perplexity-ai/ai-sdk": "^0.1.2",
"@viem/anvil": "^0.0.10", "@viem/anvil": "^0.0.10",
"ai": "^6.0.5", "ai": "^6.0.5",
"alkahest-ts": "^0.6.1", "alkahest-ts": "0.7.2",
"arktype": "^2.1.23", "arktype": "^2.1.23",
"viem": "^2.42.1", "viem": "^2.42.1",
"zod": "^3.25.76" "zod": "^3.25.76"

View File

@ -1,15 +1,11 @@
import { afterAll, beforeAll, beforeEach, expect, test } from "bun:test"; import { afterAll, beforeAll, beforeEach, expect, test } from "bun:test";
import { decodeAbiParameters, encodeAbiParameters, parseAbiParameters } from "viem"; import { toHex, keccak256, fromHex } from "viem";
import { generateText } from "ai"
import { openai } from "@ai-sdk/openai"
import { import {
setupTestEnvironment, setupTestEnvironment,
type TestContext, type TestContext,
} from "alkahest-ts"; } from "alkahest-ts";
import { makeLLMClient } from ".."; import { makeLLMClient } from "..";
import { de } from "zod/v4/locales";
import { ProviderName } from "../nla"; import { ProviderName } from "../nla";
let testContext: TestContext; let testContext: TestContext;
@ -74,43 +70,54 @@ Fulfillment: {{obligation}}`,
0n, 0n,
); );
const obligationAbi = parseAbiParameters("(string item)");
const { decisions, unwatch } = const { decisions, unwatch } =
await testContext.bob.client.arbiters.general.trustedOracle.arbitrateMany( await testContext.bob.client.arbiters.general.trustedOracle.arbitrateMany(
async ({ attestation, demand }) => { async ({ attestation, demand }) => {
console.log("Arbitrating ", attestation, demand); console.log("Arbitrating ", attestation, demand);
const obligation = charlieClient.extractObligationData( const commitRevealData = testContext.bob.client.commitReveal.decode(attestation.data);
obligationAbi, const obligationItem = fromHex(commitRevealData.payload, 'string');
attestation, console.log("Obligation:", obligationItem);
);
console.log("Obligation:", obligation);
const trustedOracleDemandData = testContext.bob.client.arbiters.general.trustedOracle.decodeDemand(demand); const trustedOracleDemandData = testContext.bob.client.arbiters.general.trustedOracle.decodeDemand(demand);
const nlaDemandData = charlieClient.llm.decodeDemand(trustedOracleDemandData.data); const nlaDemandData = charlieClient.llm.decodeDemand(trustedOracleDemandData.data);
const result = await charlieClient.llm.arbitrate(nlaDemandData, obligation[0].item); const result = await charlieClient.llm.arbitrate(nlaDemandData, obligationItem);
console.log("response", result); console.log("response", result);
return result; return result;
}, },
{ {
onAfterArbitrate: async (decision) => { onAfterArbitrate: async (decision) => {
const obligation = testContext.bob.client.extractObligationData( const commitRevealData = testContext.bob.client.commitReveal.decode(decision.attestation.data);
obligationAbi, const obligationItem = fromHex(commitRevealData.payload, 'string');
decision.attestation,
);
expect(decision.attestation.uid).toEqual(fulfillment.uid); expect(decision.attestation.uid).toEqual(fulfillment.uid);
expect(obligation[0].item).toEqual("The sky appears blue today"); expect(obligationItem).toEqual("The sky appears blue today");
expect(decision.decision).toBe(true); expect(decision.decision).toBe(true);
}, },
pollingInterval: 50, pollingInterval: 50,
}, },
); );
const schema = keccak256(toHex("{item:string}"));
const salt = keccak256(toHex(crypto.randomUUID()));
const payload = toHex("The sky appears blue today");
const obligationData = { payload, salt, schema };
// Commit-reveal flow: commit, wait a block, reveal, reclaim bond
const commitment = await testContext.bob.client.commitReveal.computeCommitment(
escrow.uid,
testContext.bob.address,
obligationData,
);
await testContext.bob.client.commitReveal.commit(commitment);
await testContext.testClient.mine({ blocks: 1 });
const { attested: fulfillment } = const { attested: fulfillment } =
await testContext.bob.client.stringObligation.doObligation( await testContext.bob.client.commitReveal.doObligation(
"The sky appears blue today", obligationData,
escrow.uid, escrow.uid,
); );
await testContext.bob.client.commitReveal.reclaimBond(fulfillment.uid);
await testContext.bob.client.arbiters.general.trustedOracle.requestArbitration( await testContext.bob.client.arbiters.general.trustedOracle.requestArbitration(
fulfillment.uid, fulfillment.uid,
testContext.bob.address, testContext.bob.address,
@ -129,3 +136,128 @@ Fulfillment: {{obligation}}`,
unwatch(); unwatch();
}, { timeout: 20000 }); }, { timeout: 20000 });
test("full flow: alice escrow -> bob fulfill -> charlie arbitrate -> bob collect", async () => {
// 1. Alice deposits an escrow with a demand, designating Charlie as oracle
const arbiter = testContext.addresses.trustedOracleArbiter;
const demand = testContext.alice.client.arbiters.general.trustedOracle.encodeDemand({
oracle: testContext.charlie.address,
data: charlieClient.llm.encodeDemand({
arbitrationProvider: ProviderName.OpenRouter,
arbitrationModel: "openai/gpt-4o",
arbitrationPrompt: `Evaluate the fulfillment against the demand and decide whether the demand was validly fulfilled
Demand: {{demand}}
Fulfillment: {{obligation}}`,
demand: "Write a haiku about the ocean"
})
});
const escrowAmount = 100n;
const { attested: escrow } =
await testContext.alice.client.erc20.escrow.nonTierable.permitAndCreate(
{
address: testContext.mockAddresses.erc20A,
value: escrowAmount,
},
{ arbiter, demand },
0n,
);
expect(escrow.uid).toBeTruthy();
console.log(`✅ Alice created escrow: ${escrow.uid}`);
// 3. Charlie starts listening as oracle (set up before Bob fulfills)
let arbitrationResolved: () => void;
const arbitrationDone = new Promise<void>((resolve) => {
arbitrationResolved = resolve;
});
const { unwatch } =
await charlieClient.arbiters.general.trustedOracle.arbitrateMany(
async ({ attestation, demand: demandData }) => {
console.log(`📨 Charlie received arbitration request for: ${attestation.uid}`);
const commitRevealData = charlieClient.commitReveal.decode(attestation.data);
const obligationItem = fromHex(commitRevealData.payload, 'string');
console.log(` Obligation text: "${obligationItem}"`);
const trustedOracleDemandData = charlieClient.arbiters.general.trustedOracle.decodeDemand(demandData);
const nlaDemandData = charlieClient.llm.decodeDemand(trustedOracleDemandData.data);
console.log(` Demand: "${nlaDemandData.demand}"`);
const result = await charlieClient.llm.arbitrate(nlaDemandData, obligationItem);
console.log(` ✨ Arbitration result: ${result}`);
return result;
},
{
onAfterArbitrate: async (decision) => {
console.log(`📝 Charlie recorded decision: ${decision.decision}`);
arbitrationResolved();
},
pollingInterval: 50,
},
);
// 2. Bob fulfills the escrow using commit-reveal
const schema = keccak256(toHex("{item:string}"));
const salt = keccak256(toHex(crypto.randomUUID()));
const fulfillmentText = "Waves crash on shore\nSalt and foam kiss weathered rocks\nThe tide breathes in, out";
const payload = toHex(fulfillmentText);
const obligationData = { payload, salt, schema };
// Commit phase
const commitment = await testContext.bob.client.commitReveal.computeCommitment(
escrow.uid,
testContext.bob.address,
obligationData,
);
await testContext.bob.client.commitReveal.commit(commitment);
await testContext.testClient.mine({ blocks: 1 });
// Reveal phase
const { attested: fulfillment } =
await testContext.bob.client.commitReveal.doObligation(
obligationData,
escrow.uid,
);
expect(fulfillment.uid).toBeTruthy();
console.log(`✅ Bob created fulfillment: ${fulfillment.uid}`);
// Reclaim bond
await testContext.bob.client.commitReveal.reclaimBond(fulfillment.uid);
console.log(`✅ Bob reclaimed bond`);
// Bob requests arbitration from Charlie
await testContext.bob.client.arbiters.general.trustedOracle.requestArbitration(
fulfillment.uid,
testContext.charlie.address,
demand,
);
console.log(`📤 Bob requested arbitration from Charlie`);
// Wait for Charlie to arbitrate
await arbitrationDone;
console.log(`✅ Arbitration complete`);
// 4. Bob collects the escrow reward
const bobBalanceBefore = await testContext.testClient.getErc20Balance(
{ address: testContext.mockAddresses.erc20A },
testContext.bob.address,
);
const collectionHash = await testContext.bob.client.erc20.escrow.nonTierable.collect(
escrow.uid,
fulfillment.uid,
);
expect(collectionHash).toBeTruthy();
const bobBalanceAfter = await testContext.testClient.getErc20Balance(
{ address: testContext.mockAddresses.erc20A },
testContext.bob.address,
);
expect(bobBalanceAfter - bobBalanceBefore).toEqual(escrowAmount);
console.log(`✅ Bob collected ${escrowAmount} tokens from escrow`);
unwatch();
}, { timeout: 30000 });