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:
parent
a18648e8d2
commit
136efdbcff
|
|
@ -41,4 +41,4 @@ anvil.log
|
|||
.env
|
||||
|
||||
# Local deployment files
|
||||
cli/deployments/devnet.json
|
||||
cli/deployments/anvil.json
|
||||
|
|
@ -136,7 +136,7 @@ nla stop
|
|||
| `nla switch <env>` | Switch between environments |
|
||||
| `nla network` | Show current environment |
|
||||
|
||||
Available environments: `devnet`, `sepolia`, `base-sepolia`, `mainnet`
|
||||
Available environments: `anvil`, `sepolia`, `base-sepolia`, `mainnet`
|
||||
|
||||
### Global Options
|
||||
|
||||
|
|
@ -445,7 +445,7 @@ nla network
|
|||
nla switch mainnet
|
||||
|
||||
# Back to local development
|
||||
nla switch devnet
|
||||
nla switch anvil
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
|
@ -512,7 +512,7 @@ natural-language-agreements/
|
|||
│ │ ├── switch.ts # Switch environments
|
||||
│ │ └── wallet.ts # Wallet management
|
||||
│ └── deployments/ # Deployment addresses (generated)
|
||||
│ ├── devnet.json
|
||||
│ ├── anvil.json
|
||||
│ ├── sepolia.json
|
||||
│ ├── base-sepolia.json
|
||||
│ └── mainnet.json
|
||||
|
|
|
|||
38
bun.lock
38
bun.lock
|
|
@ -3,7 +3,7 @@
|
|||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "natural-language-agreement-extension",
|
||||
"name": "nla",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^3.0.2",
|
||||
"@ai-sdk/openai": "^3.0.2",
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"@perplexity-ai/ai-sdk": "^0.1.2",
|
||||
"@viem/anvil": "^0.0.10",
|
||||
"ai": "^6.0.5",
|
||||
"alkahest-ts": "^0.6.1",
|
||||
"alkahest-ts": "0.7.2",
|
||||
"arktype": "^2.1.23",
|
||||
"viem": "^2.42.1",
|
||||
"zod": "^3.25.76",
|
||||
|
|
@ -29,15 +29,15 @@
|
|||
"packages": {
|
||||
"@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=="],
|
||||
|
||||
|
|
@ -65,25 +65,25 @@
|
|||
|
||||
"@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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
|
||||
"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=="],
|
||||
|
||||
|
|
@ -137,20 +137,18 @@
|
|||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ Monitor the escrow and arbitration progress:
|
|||
```bash
|
||||
nla escrow:status \
|
||||
--escrow-uid 0x... \
|
||||
--deployment ./cli/deployments/devnet.json
|
||||
--deployment ./cli/deployments/anvil.json
|
||||
```
|
||||
|
||||
This will show:
|
||||
|
|
@ -185,7 +185,7 @@ Options:
|
|||
--token <address> ERC20 token address (required)
|
||||
--oracle <address> Oracle address (required)
|
||||
--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)
|
||||
--help, -h Show help
|
||||
```
|
||||
|
|
@ -200,7 +200,7 @@ Options:
|
|||
--fulfillment <text> Your fulfillment text (required)
|
||||
--oracle <address> Oracle address (required)
|
||||
--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)
|
||||
--help, -h Show help
|
||||
```
|
||||
|
|
@ -214,7 +214,7 @@ Options:
|
|||
--escrow-uid <uid> Escrow UID (required)
|
||||
--fulfillment-uid <uid> Approved fulfillment UID (required)
|
||||
--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)
|
||||
--help, -h Show help
|
||||
```
|
||||
|
|
@ -226,7 +226,7 @@ nla escrow:status [options]
|
|||
|
||||
Options:
|
||||
--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)
|
||||
--help, -h Show help
|
||||
```
|
||||
|
|
@ -293,7 +293,7 @@ nla escrow:fulfill \
|
|||
# Terminal 2: Check the status
|
||||
nla escrow:status \
|
||||
--escrow-uid 0xd9e1402e96c2f7a64e60bf53a45445f7254e9b72389f6ede25181bff542d7b65 \
|
||||
--deployment ./cli/deployments/devnet.json
|
||||
--deployment ./cli/deployments/anvil.json
|
||||
|
||||
# Terminal 2: Collect the escrow (as Bob)
|
||||
nla escrow:collect \
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ Options:
|
|||
--escrow-uid <uid> Escrow UID to collect (required)
|
||||
--fulfillment-uid <uid> Fulfillment UID that was approved (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)
|
||||
--help, -h Display this help message
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
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 { existsSync, readFileSync } from "fs";
|
||||
import { resolve, dirname, join } from "path";
|
||||
|
|
@ -35,7 +35,7 @@ Options:
|
|||
--fulfillment <text> Your fulfillment text (required)
|
||||
--oracle <address> Oracle address that will arbitrate (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)
|
||||
--help, -h Display this help message
|
||||
|
||||
|
|
@ -170,11 +170,40 @@ async function main() {
|
|||
|
||||
console.log("📋 Creating fulfillment obligation...\n");
|
||||
|
||||
// Create the fulfillment
|
||||
const { attested: fulfillmentAttestation } = await client.stringObligation.doObligation(
|
||||
fulfillment,
|
||||
// Create the fulfillment using CommitRevealObligation (commit-reveal flow)
|
||||
const schema = keccak256(toHex("{item:string}"));
|
||||
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}`,
|
||||
);
|
||||
|
||||
// Step 4: Reclaim bond
|
||||
console.log("💰 Reclaiming bond...");
|
||||
await client.commitReveal.reclaimBond(fulfillmentAttestation.uid);
|
||||
|
||||
console.log("✅ Fulfillment created!\n");
|
||||
console.log("📋 Fulfillment Details:");
|
||||
console.log(` UID: ${fulfillmentAttestation.uid}`);
|
||||
|
|
|
|||
|
|
@ -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}════════════════════════════════════════════════════════${colors.reset}\n`);
|
||||
|
||||
// Auto-switch to devnet environment
|
||||
// Auto-switch to anvil environment
|
||||
const currentEnv = getCurrentEnvironment();
|
||||
if (currentEnv !== 'devnet') {
|
||||
console.log(`${colors.yellow}🔄 Switching environment from ${currentEnv} to devnet...${colors.reset}`);
|
||||
setCurrentEnvironment('devnet');
|
||||
console.log(`${colors.green}✅ Switched to devnet${colors.reset}\n`);
|
||||
if (currentEnv !== 'anvil') {
|
||||
console.log(`${colors.yellow}🔄 Switching environment from ${currentEnv} to anvil...${colors.reset}`);
|
||||
setCurrentEnvironment('anvil');
|
||||
console.log(`${colors.green}✅ Switched to anvil${colors.reset}\n`);
|
||||
} 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
|
||||
|
|
@ -188,8 +188,8 @@ export async function runDevCommand(cliDir: string, envPath?: string, cliPrivate
|
|||
const oracleScript = join(cliDir, 'server', 'oracle.js');
|
||||
|
||||
// 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 distPath = join(cliDir, 'deployments', 'devnet.json');
|
||||
const sourcePath = join(process.cwd(), 'cli', 'deployments', 'anvil.json');
|
||||
const distPath = join(cliDir, 'deployments', 'anvil.json');
|
||||
const deploymentFile = existsSync(sourcePath) ? sourcePath : distPath;
|
||||
|
||||
const oracleArgs = ['run', oracleScript, '--deployment', deploymentFile];
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export function runSwitchCommand(env?: string) {
|
|||
const current = getCurrentEnvironment();
|
||||
console.log(`${colors.blue}Current environment:${colors.reset} ${colors.green}${current}${colors.reset}\n`);
|
||||
console.log('Available environments:');
|
||||
console.log(' • devnet (local Anvil blockchain)');
|
||||
console.log(' • anvil (local Anvil blockchain)');
|
||||
console.log(' • sepolia (Ethereum Sepolia testnet)');
|
||||
console.log(' • base-sepolia (Base Sepolia testnet)');
|
||||
console.log(' • mainnet (Ethereum mainnet)\n');
|
||||
|
|
@ -43,10 +43,10 @@ export function runSwitchCommand(env?: string) {
|
|||
}
|
||||
|
||||
// Validate environment
|
||||
const validEnvs = ['devnet', 'sepolia', 'base-sepolia', 'mainnet'];
|
||||
const validEnvs = ['anvil', 'sepolia', 'base-sepolia', 'mainnet'];
|
||||
if (!validEnvs.includes(env)) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ export function runSwitchCommand(env?: string) {
|
|||
console.log(`${colors.green}✅ Switched to ${env}${colors.reset}\n`);
|
||||
|
||||
// Show info about the environment
|
||||
if (env === 'devnet') {
|
||||
if (env === 'anvil') {
|
||||
console.log('📝 Using local Anvil blockchain (http://localhost:8545)');
|
||||
console.log(' Run "nla dev" to start the development environment\n');
|
||||
} else if (env === 'sepolia') {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"attestationEscrowObligation": "",
|
||||
"attestationEscrowObligation2": "",
|
||||
"attestationBarterUtils": "",
|
||||
"stringObligation": "",
|
||||
"commitRevealObligation": "",
|
||||
"trivialArbiter": "",
|
||||
"trustedOracleArbiter": "",
|
||||
"anyArbiter": "",
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"attestationEscrowObligation": "",
|
||||
"attestationEscrowObligation2": "",
|
||||
"attestationBarterUtils": "",
|
||||
"stringObligation": "",
|
||||
"commitRevealObligation": "",
|
||||
"trivialArbiter": "",
|
||||
"trustedOracleArbiter": "",
|
||||
"anyArbiter": "",
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ Commands:
|
|||
deploy Deploy contracts to blockchain
|
||||
start-oracle Start the oracle service
|
||||
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
|
||||
wallet:set Set wallet private key
|
||||
wallet:show Show current wallet address
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ async function main() {
|
|||
const SchemaRegistry = fixtures.SchemaRegistry;
|
||||
const MockERC20Permit = fixtures.MockERC20Permit;
|
||||
const TrustedOracleArbiter = contracts.TrustedOracleArbiter;
|
||||
const StringObligation = contracts.StringObligation;
|
||||
const CommitRevealObligation = contracts.CommitRevealObligation;
|
||||
const ERC20EscrowObligation = contracts.ERC20EscrowObligation;
|
||||
const ERC20PaymentObligation = contracts.ERC20PaymentObligation;
|
||||
const ERC20BarterUtils = contracts.ERC20BarterUtils;
|
||||
|
|
@ -229,10 +229,10 @@ async function main() {
|
|||
// Deploy obligations
|
||||
console.log("📋 Deploying obligations...\n");
|
||||
|
||||
addresses.stringObligation = await deployContract(
|
||||
"String Obligation",
|
||||
StringObligation.abi.abi,
|
||||
StringObligation.abi.bytecode.object,
|
||||
addresses.commitRevealObligation = await deployContract(
|
||||
"Commit Reveal Obligation",
|
||||
CommitRevealObligation.abi.abi,
|
||||
CommitRevealObligation.abi.bytecode.object,
|
||||
[addresses.eas, addresses.easSchemaRegistry]
|
||||
);
|
||||
|
||||
|
|
@ -338,8 +338,8 @@ async function main() {
|
|||
const scriptDir = import.meta.dir;
|
||||
const projectRoot = resolve(scriptDir, "../..");
|
||||
|
||||
// Map localhost to devnet for file naming
|
||||
const deploymentFileName = network === "localhost" ? "devnet" : network;
|
||||
// Map localhost to anvil for file naming
|
||||
const deploymentFileName = network === "localhost" ? "anvil" : network;
|
||||
const outputPath = args.output || resolve(projectRoot, `cli/deployments/${deploymentFileName}.json`);
|
||||
const outputDir = resolve(outputPath, "..");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
import { parseArgs } from "util";
|
||||
import { parseAbiParameters, createWalletClient, http, publicActions } from "viem";
|
||||
import { createWalletClient, http, publicActions, fromHex } from "viem";
|
||||
import { privateKeyToAccount } from "viem/accounts";
|
||||
import { makeLLMClient } from "../..";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
|
|
@ -250,22 +250,16 @@ async function main() {
|
|||
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.arbiters.general.trustedOracle.arbitrateMany(
|
||||
async ({ attestation, demand }) => {
|
||||
console.log(`\n📨 New arbitration request received!`);
|
||||
console.log(` Attestation UID: ${attestation.uid}`);
|
||||
|
||||
|
||||
try {
|
||||
// Extract obligation data
|
||||
const obligation = client.extractObligationData(
|
||||
obligationAbi,
|
||||
attestation,
|
||||
);
|
||||
const obligationItem = obligation[0].item;
|
||||
// Extract obligation data from CommitRevealObligation
|
||||
const commitRevealData = client.commitReveal.decode(attestation.data);
|
||||
const obligationItem = fromHex(commitRevealData.payload, 'string');
|
||||
console.log(` Obligation: "${obligationItem}"`);
|
||||
|
||||
const trustedOracleDemandData = client.arbiters.general.trustedOracle.decodeDemand(demand);
|
||||
|
|
@ -324,7 +318,7 @@ async function main() {
|
|||
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
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(" export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80\n");
|
||||
} else {
|
||||
|
|
@ -337,7 +331,7 @@ async function main() {
|
|||
console.log(" --demand \"The sky is blue\" \\");
|
||||
console.log(" --amount 10 \\");
|
||||
|
||||
if (currentEnv === 'devnet' && deployment.addresses.mockERC20A) {
|
||||
if (currentEnv === 'anvil' && deployment.addresses.mockERC20A) {
|
||||
console.log(` --token ${deployment.addresses.mockERC20A} \\`);
|
||||
} else {
|
||||
console.log(" --token <ERC20_TOKEN_ADDRESS> \\");
|
||||
|
|
|
|||
12
cli/utils.ts
12
cli/utils.ts
|
|
@ -62,7 +62,7 @@ export function getChainFromNetwork(network: string): Chain {
|
|||
|
||||
switch (normalized) {
|
||||
case "localhost":
|
||||
case "devnet":
|
||||
case "anvil":
|
||||
return foundry;
|
||||
case "sepolia":
|
||||
case "ethereum-sepolia":
|
||||
|
|
@ -95,15 +95,15 @@ export function getCurrentEnvironment(): string {
|
|||
const configPath = join(getNLAConfigDir(), 'config.json');
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
// Default to devnet
|
||||
return 'devnet';
|
||||
// Default to anvil
|
||||
return 'anvil';
|
||||
}
|
||||
|
||||
try {
|
||||
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
||||
return config.environment || 'devnet';
|
||||
return config.environment || 'anvil';
|
||||
} catch (e) {
|
||||
return 'devnet';
|
||||
return 'anvil';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ export function clearPrivateKey(): void {
|
|||
/**
|
||||
* Get deployment path for environment
|
||||
* 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 {
|
||||
const environment = env || getCurrentEnvironment();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"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",
|
||||
"type": "module",
|
||||
"private": false,
|
||||
|
|
@ -66,7 +66,7 @@
|
|||
"@perplexity-ai/ai-sdk": "^0.1.2",
|
||||
"@viem/anvil": "^0.0.10",
|
||||
"ai": "^6.0.5",
|
||||
"alkahest-ts": "^0.6.1",
|
||||
"alkahest-ts": "0.7.2",
|
||||
"arktype": "^2.1.23",
|
||||
"viem": "^2.42.1",
|
||||
"zod": "^3.25.76"
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
import { afterAll, beforeAll, beforeEach, expect, test } from "bun:test";
|
||||
import { decodeAbiParameters, encodeAbiParameters, parseAbiParameters } from "viem";
|
||||
|
||||
import { generateText } from "ai"
|
||||
import { openai } from "@ai-sdk/openai"
|
||||
import { toHex, keccak256, fromHex } from "viem";
|
||||
|
||||
import {
|
||||
setupTestEnvironment,
|
||||
type TestContext,
|
||||
} from "alkahest-ts";
|
||||
import { makeLLMClient } from "..";
|
||||
import { de } from "zod/v4/locales";
|
||||
import { ProviderName } from "../nla";
|
||||
|
||||
let testContext: TestContext;
|
||||
|
|
@ -74,43 +70,54 @@ Fulfillment: {{obligation}}`,
|
|||
0n,
|
||||
);
|
||||
|
||||
const obligationAbi = parseAbiParameters("(string item)");
|
||||
const { decisions, unwatch } =
|
||||
await testContext.bob.client.arbiters.general.trustedOracle.arbitrateMany(
|
||||
async ({ attestation, demand }) => {
|
||||
console.log("Arbitrating ", attestation, demand);
|
||||
const obligation = charlieClient.extractObligationData(
|
||||
obligationAbi,
|
||||
attestation,
|
||||
);
|
||||
console.log("Obligation:", obligation);
|
||||
const commitRevealData = testContext.bob.client.commitReveal.decode(attestation.data);
|
||||
const obligationItem = fromHex(commitRevealData.payload, 'string');
|
||||
console.log("Obligation:", obligationItem);
|
||||
const trustedOracleDemandData = testContext.bob.client.arbiters.general.trustedOracle.decodeDemand(demand);
|
||||
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);
|
||||
return result;
|
||||
},
|
||||
{
|
||||
onAfterArbitrate: async (decision) => {
|
||||
const obligation = testContext.bob.client.extractObligationData(
|
||||
obligationAbi,
|
||||
decision.attestation,
|
||||
);
|
||||
const commitRevealData = testContext.bob.client.commitReveal.decode(decision.attestation.data);
|
||||
const obligationItem = fromHex(commitRevealData.payload, 'string');
|
||||
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);
|
||||
},
|
||||
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 } =
|
||||
await testContext.bob.client.stringObligation.doObligation(
|
||||
"The sky appears blue today",
|
||||
await testContext.bob.client.commitReveal.doObligation(
|
||||
obligationData,
|
||||
escrow.uid,
|
||||
);
|
||||
|
||||
await testContext.bob.client.commitReveal.reclaimBond(fulfillment.uid);
|
||||
|
||||
await testContext.bob.client.arbiters.general.trustedOracle.requestArbitration(
|
||||
fulfillment.uid,
|
||||
testContext.bob.address,
|
||||
|
|
@ -129,3 +136,128 @@ Fulfillment: {{obligation}}`,
|
|||
|
||||
unwatch();
|
||||
}, { 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 });
|
||||
|
|
|
|||
Loading…
Reference in New Issue