From 00105281b49ba5a2e12a0998b3da3b648d5a8231 Mon Sep 17 00:00:00 2001 From: Anil Kumar <150643132+anil-rome@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:19:14 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20verify=20=E2=80=94=20the=20both-lane=20?= =?UTF-8?q?works-gate=20(deploy=20probe=20=E2=86=92=20EVM=20+=20Solana=20l?= =?UTF-8?q?ane=20=E2=86=92=20assert=20parity)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `rome verify --path solidity` deploys a Store probe and proves the SAME contract answers when set from the EVM lane (submitRomeTx) AND the Solana lane (submitRomeTxSolanaLane) — the litmus test made runnable. Funded ACTION: ROME_EVM_KEY + ROME_SOLANA_KEY (env-only), CLI-only, never MCP. Adds a Solana key reader + @solana/web3.js. Live-verified GREEN on Hadrian (probe 0x368744e9, EVM 42/42, Solana 43/43). --- README.md | 1 + package-lock.json | 104 +++++++--------------------------- package.json | 1 + src/core/capabilities.ts | 10 ++++ src/core/keys.ts | 28 ++++++++++ src/core/probe.ts | 11 ++++ src/core/verify.ts | 117 +++++++++++++++++++++++++++++++++++++++ test/verify.test.ts | 74 +++++++++++++++++++++++++ 8 files changed, 262 insertions(+), 84 deletions(-) create mode 100644 src/core/probe.ts create mode 100644 src/core/verify.ts create mode 100644 test/verify.test.ts diff --git a/README.md b/README.md index ce453e2..e06836c 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ rome deploy hadrian ./out/Store.json # deploy a compiled artifact rome send hadrian 0x… "set(uint256)" 42 # write via submitRomeTx rome fund hadrian --from base-sepolia --amount 1 # bridge USDC → Rome gas (CCTP, "from home") rome bridge hadrian --from base-sepolia --amount 1 --intent wrapper # USDC → wUSDC on Rome +rome verify hadrian --path solidity # both-lane works-gate (needs ROME_EVM_KEY + ROME_SOLANA_KEY) ``` Chains resolve by id, name, or slug (`200010`, `hadrian`, `200010-hadrian`). Output is JSON — pipe it to `jq` or read it in an agent: diff --git a/package-lock.json b/package-lock.json index 0afa5c6..fc163a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,22 @@ { "name": "@rome-protocol/cli", - "version": "0.1.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rome-protocol/cli", - "version": "0.1.0", + "version": "0.3.0", "dependencies": { "@modelcontextprotocol/sdk": "^1.29.0", "@rome-protocol/registry": "github:rome-protocol/rome-registry#v0.1.0", "@rome-protocol/sdk": "github:rome-protocol/rome-sdk-ts#v0.2.1", + "@solana/web3.js": "^1.98.0", "viem": "^2.21.0", "zod": "^3.23.0" }, "bin": { - "rome": "dist/cli.js" + "rome": "dist/bin.js" }, "devDependencies": { "@types/node": "^22.0.0", @@ -38,7 +39,6 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -1028,7 +1028,6 @@ "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", "license": "MIT", - "peer": true, "dependencies": { "buffer": "~6.0.3" }, @@ -1218,7 +1217,6 @@ "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz", "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.25.0", "@noble/curves": "^1.4.2", @@ -1242,7 +1240,6 @@ "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", "license": "MIT", - "peer": true, "dependencies": { "@solana/errors": "2.3.0" }, @@ -1258,7 +1255,6 @@ "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", "license": "MIT", - "peer": true, "dependencies": { "@solana/codecs-core": "2.3.0", "@solana/errors": "2.3.0" @@ -1275,7 +1271,6 @@ "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", "license": "MIT", - "peer": true, "dependencies": { "chalk": "^5.4.1", "commander": "^14.0.0" @@ -1295,7 +1290,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "license": "MIT", - "peer": true, "engines": { "node": ">=20" } @@ -1305,7 +1299,6 @@ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -1315,7 +1308,6 @@ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -1340,15 +1332,13 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/ws": { "version": "7.4.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -1505,7 +1495,6 @@ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", "license": "MIT", - "peer": true, "dependencies": { "humanize-ms": "^1.2.1" }, @@ -1561,7 +1550,6 @@ "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "^5.0.1" } @@ -1584,8 +1572,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/bigint-buffer": { "version": "1.1.5", @@ -1625,8 +1612,7 @@ "version": "5.2.5", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz", "integrity": "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/body-parser": { "version": "2.3.0", @@ -1670,7 +1656,6 @@ "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "bn.js": "^5.2.0", "bs58": "^4.0.0", @@ -1682,7 +1667,6 @@ "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", "license": "MIT", - "peer": true, "dependencies": { "base-x": "^3.0.2" } @@ -1706,7 +1690,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -1719,7 +1702,6 @@ "hasInstallScript": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -1797,7 +1779,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", - "peer": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -1928,7 +1909,6 @@ "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -2015,15 +1995,13 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/es6-promisify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", "license": "MIT", - "peer": true, "dependencies": { "es6-promise": "^4.0.3" } @@ -2099,8 +2077,7 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/eventsource": { "version": "3.0.7", @@ -2199,7 +2176,6 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", - "peer": true, "engines": { "node": "> 0.1.90" } @@ -2214,8 +2190,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-uri": { "version": "3.1.4", @@ -2417,7 +2392,6 @@ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.0.0" } @@ -2456,8 +2430,7 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/inherits": { "version": "2.0.4", @@ -2500,7 +2473,6 @@ "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", "license": "MIT", - "peer": true, "peerDependencies": { "ws": "*" } @@ -2525,7 +2497,6 @@ "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.3.0.tgz", "integrity": "sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/connect": "^3.4.33", "@types/node": "^12.12.54", @@ -2551,15 +2522,13 @@ "version": "12.20.55", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jayson/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jose": { "version": "6.2.3", @@ -2586,8 +2555,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/loupe": { "version": "3.2.1", @@ -2700,7 +2668,6 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "license": "MIT", - "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -2722,7 +2689,6 @@ "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "license": "MIT", "optional": true, - "peer": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -3044,7 +3010,6 @@ "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.3.9.tgz", "integrity": "sha512-2iQDaTB4g5fDB2ihrTFSJSibCEuxaRi1q7qTW7ZO9/M5/TC+ToHA4D9/ffNLEbAoHNNrcdeP05oATNk44SKZXA==", "license": "LGPL-3.0-only", - "peer": true, "dependencies": { "@swc/helpers": "^0.5.11", "@types/uuid": "^10.0.0", @@ -3068,7 +3033,6 @@ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -3080,7 +3044,6 @@ "hasInstallScript": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -3097,7 +3060,6 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", - "peer": true, "bin": { "uuid": "dist-node/bin/uuid" } @@ -3107,7 +3069,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -3142,8 +3103,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -3339,15 +3299,13 @@ "version": "2.2.5", "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/stream-json": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "stream-chain": "^2.2.5" } @@ -3357,7 +3315,6 @@ "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", "license": "MIT", - "peer": true, "engines": { "node": ">=14.0.0" } @@ -3365,8 +3322,7 @@ "node_modules/text-encoding-utf-8": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", - "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==", - "peer": true + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" }, "node_modules/tinybench": { "version": "2.9.0", @@ -3425,15 +3381,13 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tsx": { "version": "4.23.1", @@ -3513,27 +3467,12 @@ "node": ">= 0.8" } }, - "node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "extraneous": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", "license": "MIT", - "peer": true, "bin": { "uuid": "dist/bin/uuid" } @@ -4196,15 +4135,13 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "license": "MIT", - "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -4253,7 +4190,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, diff --git a/package.json b/package.json index 474b906..e3b9a79 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@modelcontextprotocol/sdk": "^1.29.0", "@rome-protocol/registry": "github:rome-protocol/rome-registry#v0.1.0", "@rome-protocol/sdk": "github:rome-protocol/rome-sdk-ts#v0.2.1", + "@solana/web3.js": "^1.98.0", "viem": "^2.21.0", "zod": "^3.23.0" }, diff --git a/src/core/capabilities.ts b/src/core/capabilities.ts index 31cffc7..a24d9d1 100644 --- a/src/core/capabilities.ts +++ b/src/core/capabilities.ts @@ -4,6 +4,7 @@ import { callContract, deployContract, sendContract } from "./actions.js"; import { fundHandler, bridgeHandler } from "./bridge.js"; import { doctor } from "./doctor.js"; import { diagnoseTx } from "./tx.js"; +import { verifyHandler } from "./verify.js"; import { type Deps } from "./deps.js"; export interface ArgSpec { @@ -198,6 +199,15 @@ export const CAPABILITIES: Capability[] = [ ], (a) => bridgeHandler(a), ), + + // ── verify: the both-lane works-gate (CLI-only action; EVM key + Solana key) ── + verbAction( + "verify", + "verify", + "The both-lane works-gate: deploy a probe and prove the SAME contract answers on the EVM lane AND the Solana lane. Needs ROME_EVM_KEY + ROME_SOLANA_KEY. e.g. rome verify hadrian --path solidity", + [chainArg, { name: "path", required: false, description: "builder path (solidity; solana-program + from-home follow)" }], + (a) => verifyHandler(a), + ), ]; export function findCapability(group: string, command: string): Capability | undefined { diff --git a/src/core/keys.ts b/src/core/keys.ts index 49016ca..571e7ab 100644 --- a/src/core/keys.ts +++ b/src/core/keys.ts @@ -1,4 +1,7 @@ +import { Keypair } from "@solana/web3.js"; + export const EVM_KEY_ENV = "ROME_EVM_KEY"; +export const SOLANA_KEY_ENV = "ROME_SOLANA_KEY"; /** * The signing key for actions, read from the environment ONLY — never a flag, never @@ -19,3 +22,28 @@ export function requireEvmKey(): `0x${string}` { } return key; } + +/** + * The Solana-lane signing key for `verify` (drives an EVM contract from a Solana + * wallet), read from the environment ONLY — same rules as the EVM key. Accepts the + * standard 64-byte secret-key JSON array (solana-keygen format). + */ +export function requireSolanaKey(): Keypair { + const raw = process.env[SOLANA_KEY_ENV]; + if (!raw) { + throw new Error( + `No Solana signing key. Set ${SOLANA_KEY_ENV} in your environment (a JSON array of the 64-byte secret key). ` + + `Read from the environment only — never a flag, never logged, never sent through MCP.`, + ); + } + let bytes: Uint8Array; + try { + const arr = JSON.parse(raw.trim()); + if (!Array.isArray(arr)) throw new Error("not an array"); + bytes = Uint8Array.from(arr); + } catch { + throw new Error(`${SOLANA_KEY_ENV} must be a JSON array of the 64-byte Solana secret key.`); + } + if (bytes.length !== 64) throw new Error(`${SOLANA_KEY_ENV} must be 64 bytes (got ${bytes.length}).`); + return Keypair.fromSecretKey(bytes); +} diff --git a/src/core/probe.ts b/src/core/probe.ts new file mode 100644 index 0000000..1d58c85 --- /dev/null +++ b/src/core/probe.ts @@ -0,0 +1,11 @@ +// A minimal `Store` probe (solc 0.8.36, optimized): `set(uint256)` / `get()→uint256`. +// `rome verify` deploys it, then drives set/get from BOTH lanes to prove the same +// contract answers on the EVM lane and the Solana lane. Source: scratchpad probe/Store.sol. +export const STORE_PROBE = { + abi: [ + { inputs: [], name: "get", outputs: [{ internalType: "uint256", name: "", type: "uint256" }], stateMutability: "view", type: "function" }, + { inputs: [{ internalType: "uint256", name: "x", type: "uint256" }], name: "set", outputs: [], stateMutability: "nonpayable", type: "function" }, + ], + bytecode: + "0x6080604052348015600e575f5ffd5b5060a580601a5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c806360fe47b11460345780636d4ce63c146045575b5f5ffd5b6043603f3660046059565b5f55565b005b5f5460405190815260200160405180910390f35b5f602082840312156068575f5ffd5b503591905056fea2646970667358221220960bbcb62c68f5e98b5bca2ceac80403075d01943318403b5e8bcf09cff64a0b64736f6c63430008240033", +} as const; diff --git a/src/core/verify.ts b/src/core/verify.ts new file mode 100644 index 0000000..eb6a037 --- /dev/null +++ b/src/core/verify.ts @@ -0,0 +1,117 @@ +import { createPublicClient, createWalletClient, http, encodeFunctionData } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { Connection, type Transaction } from "@solana/web3.js"; +import { submitRomeTx, submitRomeTxSolanaLane } from "@rome-protocol/sdk"; +import { getChainFacts } from "./facts.js"; +import { requireEvmKey, requireSolanaKey } from "./keys.js"; +import { eip1193FromAccount } from "./eip1193.js"; +import { STORE_PROBE } from "./probe.js"; + +// `rome verify --path solidity` — the both-lane works-gate. Deploy a probe, then prove +// the SAME contract answers when set from the EVM lane (submitRomeTx) AND the Solana lane +// (submitRomeTxSolanaLane). A funded ACTION (needs an EVM key + a Solana key); CLI-only, +// never MCP. The lane ops are injectable so the orchestration is unit-testable. + +export interface VerifyCheck { + lane: "evm" | "solana"; + wrote: string; + read: string; + ok: boolean; +} +export interface VerifyResult { + path: "solidity"; + probe: string; + checks: VerifyCheck[]; + ok: boolean; +} + +export interface VerifyDeps { + deployProbe(): Promise<`0x${string}`>; + evmLaneSet(probe: `0x${string}`, value: bigint): Promise; + solanaLaneSet(probe: `0x${string}`, value: bigint): Promise; + read(probe: `0x${string}`): Promise; +} + +const EVM_VALUE = 42n; +const SOLANA_VALUE = 43n; + +/** Deploy a probe, then prove the SAME contract answers when set from each lane. */ +export async function runVerifySolidity(deps: VerifyDeps): Promise { + const probe = await deps.deployProbe(); + const checks: VerifyCheck[] = []; + + await deps.evmLaneSet(probe, EVM_VALUE); + const r1 = await deps.read(probe); + checks.push({ lane: "evm", wrote: EVM_VALUE.toString(), read: r1.toString(), ok: r1 === EVM_VALUE }); + + await deps.solanaLaneSet(probe, SOLANA_VALUE); + const r2 = await deps.read(probe); + checks.push({ lane: "solana", wrote: SOLANA_VALUE.toString(), read: r2.toString(), ok: r2 === SOLANA_VALUE }); + + return { path: "solidity", probe, checks, ok: checks.every((c) => c.ok) }; +} + +/** Real lane ops: viem deploy + submitRomeTx (EVM lane) + submitRomeTxSolanaLane (Solana lane). */ +export function defaultVerifyDeps(chain: string | number): VerifyDeps { + const c = getChainFacts(chain); + const account = privateKeyToAccount(requireEvmKey()); // fail fast — no network + const solKeypair = requireSolanaKey(); // fail fast + const pub = createPublicClient({ transport: http(c.rpcUrl) }); + const provider = eip1193FromAccount(account, c.rpcUrl, c.chainId); + const solanaRpc = (c as { solana?: { rpc?: string } }).solana?.rpc ?? "https://api.devnet.solana.com"; + const connection = new Connection(solanaRpc, "confirmed"); + const setData = (value: bigint) => encodeFunctionData({ abi: STORE_PROBE.abi, functionName: "set", args: [value] }); + + return { + async deployProbe() { + const wallet = createWalletClient({ account, transport: http(c.rpcUrl) }); + const gp = await pub.getGasPrice(); + const hash = await wallet.deployContract({ + abi: STORE_PROBE.abi as never, + bytecode: STORE_PROBE.bytecode, + chain: null, + gas: 26_000_000n, + maxFeePerGas: (gp * 3n) / 2n, + maxPriorityFeePerGas: 0n, + }); + const rcpt = await pub.waitForTransactionReceipt({ hash }); + if (!rcpt.contractAddress) throw new Error("probe deploy produced no contract address"); + return rcpt.contractAddress; + }, + async evmLaneSet(probe, value) { + const hash = await submitRomeTx(provider, { from: account.address, to: probe, data: setData(value) }); + await pub.waitForTransactionReceipt({ hash }); + }, + async solanaLaneSet(probe, value) { + await submitRomeTxSolanaLane( + { + connection, + proxyUrl: c.rpcUrl, + programId: c.romeEvmProgramId, + chainId: c.chainId, + payer: solKeypair.publicKey, + signTransaction: async (tx: Transaction) => { + tx.partialSign(solKeypair); + return tx; + }, + }, + { to: probe, data: setData(value), autoProvision: true }, + ); + }, + async read(probe) { + const data = encodeFunctionData({ abi: STORE_PROBE.abi, functionName: "get" }); + const { data: ret } = await pub.call({ to: probe, data }); + return BigInt(ret ?? "0x0"); + }, + }; +} + +/** `rome verify --path solidity` handler — validates the path, then runs the gate. */ +export async function verifyHandler(args: Record): Promise { + const path = (args.path ?? "solidity").toLowerCase(); + if (path !== "solidity") { + throw new Error(`Only --path solidity is supported in this slice (got "${path}"). solana-program + from-home follow.`); + } + // async so a synchronous key/config throw surfaces as a rejected promise, not a throw. + return runVerifySolidity(defaultVerifyDeps(args.chain)); +} diff --git a/test/verify.test.ts b/test/verify.test.ts new file mode 100644 index 0000000..c83e524 --- /dev/null +++ b/test/verify.test.ts @@ -0,0 +1,74 @@ +import { describe, it, expect, vi } from "vitest"; +import { runVerifySolidity, verifyHandler, type VerifyDeps } from "../src/core/verify.js"; +import { CAPABILITIES } from "../src/core/capabilities.js"; +import { buildMcpTools } from "../src/mcp.js"; + +// `verify --path solidity` — the both-lane works-gate: deploy a probe, drive set/get +// from the EVM lane (submitRomeTx) AND the Solana lane (submitRomeTxSolanaLane), assert +// the SAME contract answers on each. A funded ACTION (both keys); CLI-only, never MCP. +// These tests pin the orchestration + parity logic with mocked lane ops (no network). + +function deps(reads: bigint[], order: string[]): VerifyDeps { + let i = 0; + return { + deployProbe: vi.fn(async () => { + order.push("deploy"); + return "0xprobe00000000000000000000000000000000abcd" as `0x${string}`; + }), + evmLaneSet: vi.fn(async () => void order.push("evmSet")), + solanaLaneSet: vi.fn(async () => void order.push("solanaSet")), + read: vi.fn(async () => { + order.push("read"); + return reads[i++]; + }), + }; +} + +describe("runVerifySolidity — both-lane works-gate", () => { + it("GREEN when both lanes drive the same contract (deploy → evm set/read → solana set/read)", async () => { + const order: string[] = []; + const r = await runVerifySolidity(deps([42n, 43n], order)); + expect(order).toEqual(["deploy", "evmSet", "read", "solanaSet", "read"]); + expect(r.ok).toBe(true); + expect(r.probe).toMatch(/^0x/); + expect(r.checks.map((c) => [c.lane, c.ok])).toEqual([ + ["evm", true], + ["solana", true], + ]); + }); + + it("RED when the EVM lane write doesn't land", async () => { + const r = await runVerifySolidity(deps([0n, 43n], [])); + expect(r.checks.find((c) => c.lane === "evm")?.ok).toBe(false); + expect(r.ok).toBe(false); + }); + + it("RED when the Solana lane write doesn't land", async () => { + const r = await runVerifySolidity(deps([42n, 42n], [])); + expect(r.checks.find((c) => c.lane === "solana")?.ok).toBe(false); + expect(r.ok).toBe(false); + }); +}); + +describe("verify is a key-gated action, never on MCP", () => { + it("registered as an action requiring a key, and absent from the MCP surface", () => { + const cap = CAPABILITIES.find((c) => c.id === "verify.verify"); + expect(cap?.kind).toBe("action"); + expect(cap?.requiresKey).toBe(true); + expect(cap?.verb).toBe(true); + expect(new Set(buildMcpTools().map((t) => t.name)).has("verify")).toBe(false); + }); + + it("refuses without keys before any network call", async () => { + const pe = process.env.ROME_EVM_KEY; + const ps = process.env.ROME_SOLANA_KEY; + delete process.env.ROME_EVM_KEY; + delete process.env.ROME_SOLANA_KEY; + try { + await expect(verifyHandler({ chain: "hadrian", path: "solidity" })).rejects.toThrow(/ROME_(EVM|SOLANA)_KEY/); + } finally { + if (pe !== undefined) process.env.ROME_EVM_KEY = pe; + if (ps !== undefined) process.env.ROME_SOLANA_KEY = ps; + } + }); +});