diff --git a/README.md b/README.md index 095e8531..e2473ede 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ See full behavior details: [`docs/ENS/ENS_JOB_PAGES_OVERVIEW.md`](docs/ENS/ENS_J Expected result after Prime deployment: - Premium jobs use procurement-first winner discovery before assignment (not first-touch lock capture). - Settlement retains conservative escrow/bond/dispute/finalization behavior. -- Optional ENSJobPages lifecycle hooks can be configured on Prime via `setEnsJobPages(...)` and remain best-effort/non-fatal. +- Optional ENSJobPages lifecycle hooks can be configured on Prime via `setEnsJobPages(...)` and remain best-effort/non-fatal. Prime does not expose a `useEnsJobTokenURI` flag; completion NFT metadata remains IPFS/completion-URI based unless a future, separately sized manager release changes that. Prime exposes keeper/bot-friendly autonomy surfaces for deterministic procurement progression in discovery (`AGIJobDiscoveryPrime`): `isShortlistFinalizable`, `isWinnerFinalizable`, `isFallbackPromotable`, `nextActionForProcurement`, `getAutonomyStatus`, and permissionless `advanceProcurement` for timeout-driven stage advancement. @@ -273,7 +273,7 @@ Alias note: `check-no-binaries` is exposed as `npm run check:no-binaries`. - Preview ENS values are projections from the current prefix/root configuration. - Effective ENS values are authoritative per-job snapshots stored in `ENSJobPages`. -- `AGIJobManagerPrime` remains on the existing `handleHook(uint8,uint256)` ABI; authority repair and metadata repair live on the ENS side. +- `AGIJobManagerPrime` remains on the existing `handleHook(uint8,uint256)` ABI; authority repair, migration, resolver repair, and finalization live on the ENS side. Preview getters are projections only; authoritative values come from ENSJobPages per-job snapshots. - Re-run `scripts/ens/audit-mainnet.ts` and `scripts/ens/inventory-job-pages.ts` from a networked operator environment before mainnet cutover because chain state is the source of truth. - Treat `previewJobEns*` as projected values only; treat `effectiveJobEns*` as authoritative only after the inventory/status scripts confirm authority snapshot readiness for that job. - The production-safe Prime path is **keeper-assisted authoritative ENS**, not fully automated on-chain ENS hydration: operators may need to call `createJobPage`, `onAgentAssigned`, `onCompletionRequested`, `repairAuthoritySnapshot`, `repairResolver`, `repairTexts`, `repairAuthorisations`, and `lockJobENS` using event-driven runbooks. diff --git a/contracts/ens/ENSJobPages.sol b/contracts/ens/ENSJobPages.sol index c97aa6bd..d55ca3e1 100644 --- a/contracts/ens/ENSJobPages.sol +++ b/contracts/ens/ENSJobPages.sol @@ -817,10 +817,6 @@ contract ENSJobPages is Ownable, ERC1155Holder, IENSJobPagesHooksV1 { function _setResolverBestEffort(uint8 hook, uint256 jobId, bytes32 node, address resolver) internal { if (resolver == address(0)) return; - if (!_supportsResolverInterface(address(publicResolver), RESOLVER_SETTEXT_INTERFACE_ID)) { - emit ENSHookBestEffortFailure(hook, jobId, "RESOLVER_SET_TEXT_UNSUPPORTED"); - } - if (_isWrappedNode(node)) { try IResolverManager(address(nameWrapper)).setResolver(node, resolver) { _jobResolverConfigured[jobId] = true; @@ -839,10 +835,6 @@ contract ENSJobPages is Ownable, ERC1155Holder, IENSJobPagesHooksV1 { function _setTextBestEffort(uint8 hook, uint256 jobId, bytes32 node, string memory key, string memory value) internal { if (bytes(value).length == 0) return; - if (!_supportsResolverInterface(address(publicResolver), RESOLVER_SETTEXT_INTERFACE_ID)) { - emit ENSHookBestEffortFailure(hook, jobId, "SET_TEXT_UNSUPPORTED"); - return; - } try publicResolver.setText(node, key, value) { if (keccak256(bytes(key)) == keccak256(bytes("agijobs.completion.public"))) { _jobCompletionTextConfigured[jobId] = true; @@ -854,11 +846,6 @@ contract ENSJobPages is Ownable, ERC1155Holder, IENSJobPagesHooksV1 { function _setAuthorisationBestEffort(uint8 hook, uint256 jobId, bytes32 node, address account, bool authorised) internal { if (account == address(0)) return; - if (!_supportsResolverInterface(address(publicResolver), RESOLVER_SETAUTH_INTERFACE_ID)) { - emit ENSHookBestEffortFailure(hook, jobId, "SET_AUTH_UNSUPPORTED"); - return; - } - try publicResolver.setAuthorisation(node, account, authorised) { emit JobENSPermissionsUpdated(jobId, account, authorised); } catch { @@ -1098,11 +1085,17 @@ contract ENSJobPages is Ownable, ERC1155Holder, IENSJobPagesHooksV1 { } function _resolverCapabilities() internal view returns (bool supportsText, bool supportsSetText, bool supportsSetAuthorisation) { - supportsText = _supportsResolverInterface(address(publicResolver), RESOLVER_TEXT_INTERFACE_ID); + supportsText = _supportsResolverInterface(address(publicResolver), RESOLVER_TEXT_INTERFACE_ID) || _supportsTextLookup(address(publicResolver)); supportsSetText = _supportsResolverInterface(address(publicResolver), RESOLVER_SETTEXT_INTERFACE_ID); supportsSetAuthorisation = _supportsResolverInterface(address(publicResolver), RESOLVER_SETAUTH_INTERFACE_ID); } + function _supportsTextLookup(address resolver) internal view returns (bool ok) { + if (resolver == address(0) || resolver.code.length == 0) return false; + bytes memory payload = abi.encodeWithSignature("text(bytes32,string)", bytes32(0), "schema"); + (ok, ) = resolver.staticcall(payload); + } + function _supportsResolverInterface(address resolver, bytes4 interfaceId) internal view returns (bool ok) { if (resolver == address(0) || resolver.code.length == 0) return false; bytes memory payload = abi.encodeWithSelector(SUPPORTS_INTERFACE_SELECTOR, interfaceId); diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index c867e9db..ef94caba 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -39,7 +39,7 @@ It **does not** freeze operational controls like pause, settlement pause, thresh | `ens` | `updateEnsRegistry` | Yes | identity-configurable + **empty locked balances** + nonzero | Identity gating dependency | | `nameWrapper` | `updateNameWrapper` | Yes | identity-configurable + **empty locked balances** + nonzero | Wrapped-root checks dependency | | `ensJobPages` | `setEnsJobPages` | Yes | identity-configurable; contract code required if nonzero | Enables lifecycle hooks | -| `useEnsJobTokenURI` | `setUseEnsJobTokenURI` | Yes | none | Pulls NFT tokenURI from ENSJobPages when available | +| `useEnsJobTokenURI` | Legacy manager only | No on Prime | Prime does not expose this flag | Treat as quarantined/deprecated for Prime docs; completion NFTs stay completion-URI based on current Prime deployments | | Root nodes | `updateRootNodes` | Yes | identity-configurable + **empty locked balances** | club/agent + alpha variants | | Merkle roots | `updateMerkleRoots` | Yes | not identity-locked (callable after `lockIdentityConfiguration()`) | validator/agent allowlist roots; owner can update post-lock | | Moderators | `addModerator/removeModerator` | Yes | nonzero address helper check | Dispute resolution role | diff --git a/docs/ENS/OBSERVED_MAINNET_STATE.md b/docs/ENS/OBSERVED_MAINNET_STATE.md index f19ad710..e2eaaa1d 100644 --- a/docs/ENS/OBSERVED_MAINNET_STATE.md +++ b/docs/ENS/OBSERVED_MAINNET_STATE.md @@ -46,7 +46,7 @@ _Observed on Ethereum mainnet on 2026-03-23 UTC using `scripts/ens/audit-mainnet - `configurationStatus()` - `jobAuthorityInfo(uint256)` currently revert on mainnet. -2. The live public resolver configuration is not sufficient for the intended production-grade metadata and authorisation workflow because `setText` and `setAuthorisation` support is not advertised. +2. The live public resolver does not advertise `setText` or `setAuthorisation` support over ERC-165. The replacement ENSJobPages keeps those write-capability checks as hard configuration gates so hook processing does not falsely report success while metadata/authorisation writes are impossible. 3. There are currently no Prime jobs on the observed manager deployment (`nextJobId = 0`), so there is no historical inventory to migrate yet on this address pair. ## Compatibility conclusion diff --git a/docs/REFERENCE/ENS_REFERENCE.md b/docs/REFERENCE/ENS_REFERENCE.md index 94ea2177..e0edf92b 100644 --- a/docs/REFERENCE/ENS_REFERENCE.md +++ b/docs/REFERENCE/ENS_REFERENCE.md @@ -1,7 +1,7 @@ # ENS Reference (Generated) Generated at (UTC): 1970-01-01T00:00:00Z -Source fingerprint: eb08b0407b10d626 +Source fingerprint: b2467e6ec95fafc5 Source files used: - `contracts/AGIJobManager.sol` @@ -55,9 +55,9 @@ Source files used: - `function lockJobENS(uint256 jobId, address employer, address agent, bool burnFuses) public onlyOwner` ([contracts/ens/ENSJobPages.sol#L767](../../contracts/ens/ENSJobPages.sol#L767)) - `function _lockJobENS(uint256 jobId, address employer, address agent, bool burnFuses) internal` ([contracts/ens/ENSJobPages.sol#L771](../../contracts/ens/ENSJobPages.sol#L771)) - `function _createSubname(bytes32 parentRootNode, string memory label) internal returns (bytes32 node)` ([contracts/ens/ENSJobPages.sol#L798](../../contracts/ens/ENSJobPages.sol#L798)) -- `function _isWrappedRootNode(bytes32 rootNode) internal view returns (bool)` ([contracts/ens/ENSJobPages.sol#L887](../../contracts/ens/ENSJobPages.sol#L887)) -- `function _requireWrapperAuthorization(bytes32 rootNode) internal view` ([contracts/ens/ENSJobPages.sol#L899](../../contracts/ens/ENSJobPages.sol#L899)) -- `function _registerRootVersion(bytes32 rootNode, string memory rootName) internal` ([contracts/ens/ENSJobPages.sol#L1052](../../contracts/ens/ENSJobPages.sol#L1052)) +- `function _isWrappedRootNode(bytes32 rootNode) internal view returns (bool)` ([contracts/ens/ENSJobPages.sol#L874](../../contracts/ens/ENSJobPages.sol#L874)) +- `function _requireWrapperAuthorization(bytes32 rootNode) internal view` ([contracts/ens/ENSJobPages.sol#L886](../../contracts/ens/ENSJobPages.sol#L886)) +- `function _registerRootVersion(bytes32 rootNode, string memory rootName) internal` ([contracts/ens/ENSJobPages.sol#L1039](../../contracts/ens/ENSJobPages.sol#L1039)) - `function verifyENSOwnership(` ([contracts/utils/ENSOwnership.sol#L32](../../contracts/utils/ENSOwnership.sol#L32)) - `function verifyENSOwnership(` ([contracts/utils/ENSOwnership.sol#L48](../../contracts/utils/ENSOwnership.sol#L48)) - `function verifyMerkleOwnership(address claimant, bytes32[] calldata proof, bytes32 merkleRoot)` ([contracts/utils/ENSOwnership.sol#L61](../../contracts/utils/ENSOwnership.sol#L61)) diff --git a/docs/ens-job-pages.md b/docs/ens-job-pages.md index 8d5a9bba..57daa72b 100644 --- a/docs/ens-job-pages.md +++ b/docs/ens-job-pages.md @@ -7,12 +7,12 @@ This document defines the **official ENS naming scheme** and **record layout** f One ENS name per job: ``` -job-.alpha.jobs.agi.eth +agijob-.alpha.jobs.agi.eth ``` Example: ``` -job-42.alpha.jobs.agi.eth +agijob-42.alpha.jobs.agi.eth ``` `jobId` is the on‑chain AGIJobManager job ID. @@ -100,7 +100,7 @@ When using the `ENSJobPages` helper contract, complete these wiring steps: 1. Deploy `ENSJobPages` with the ENS registry, NameWrapper (if any), PublicResolver, root node, and root name. 2. Ensure `alpha.jobs.agi.eth` is owned by the helper (or wrapped and approved for it). 3. Call `ENSJobPages.setJobManager(AGIJobManager)` so hooks are accepted. -4. Call `AGIJobManager.setEnsJobPages(ENSJobPages)` to enable hook callbacks. +4. Call `AGIJobManagerPrime.setEnsJobPages(ENSJobPages)` to enable hook callbacks. These steps keep ENS integration **opt-in** and ensure lifecycle hooks remain best-effort. @@ -112,11 +112,7 @@ These steps keep ENS integration **opt-in** and ensure lifecycle hooks remain be - Revoke resolver authorizations after terminal settlement. ## ENS job NFT tokenURI (optional) -When `AGIJobManager.setUseEnsJobTokenURI(true)` is enabled (and an ENS helper is configured), completion NFTs point to: -``` -ens://job-.alpha.jobs.agi.eth -``` -When disabled (default), the tokenURI behavior is unchanged and continues to use the completion metadata pointer. +The deployed Prime manager does not expose `setUseEnsJobTokenURI` or `useEnsJobTokenURI`. On the current Prime path, completion NFTs continue to use the completion metadata pointer/IPFS flow. `useEnsJobTokenURI` remains an ENSJobPages compatibility flag only and should be treated as quarantined documentation-wise until a separately sized manager release explicitly wires it end-to-end. ## Post‑terminal lock (optional) `AGIJobManager.lockJobENS(jobId, burnFuses)` can be called after a terminal state to re‑revoke resolver authorizations and optionally attempt fuse burning (best‑effort). @@ -131,6 +127,6 @@ Fuse burning is optional and does **not** affect settlement or withdrawals if it ## 2026-03 authority model -`previewJobEns*` getters now expose mutable projections, while `effectiveJobEns*` getters expose immutable per-job authority snapshots. Compatibility getters (`jobEnsName`, `jobEnsURI`, `jobEnsNode`) prefer authoritative values once established and only fall back to preview values before first issuance/import. Operational repairs are owner-only ENS-side actions: `repairAuthoritySnapshot`, `repairResolver`, `repairTexts`, `repairAuthorisations`, `replayCreate`, `replayAssign`, `replayCompletion`, `replayRevoke`, and `replayLock`. Chain state wins over docs, so mainnet runbooks must be regenerated from `scripts/ens/output/` before cutover. +`previewJobEns*` getters expose mutable projections built from the current prefix/root only. `effectiveJobEns*` getters expose immutable per-job authority snapshots tied to the stored root version and historical label. Compatibility getters (`jobEnsName`, `jobEnsURI`, `jobEnsNode`) prefer authoritative values once established and only fall back to preview values before first issuance/import. Operational repairs are owner-only ENS-side actions: `repairAuthoritySnapshot`, `repairResolver`, `repairTexts`, `repairAuthorisations`, `replayCreate`, `replayAssign`, `replayCompletion`, `replayRevoke`, and `replayLock`. Chain state wins over docs, so mainnet runbooks must be regenerated from `scripts/ens/output/` before cutover. Production wording matters: the current recommended path is **keeper-assisted authoritative** ENS. Prime settlement stays authoritative and non-blocking even if ENS hydration is delayed or temporarily broken, while operators use `scripts/ens/audit-mainnet.ts`, `scripts/ens/inventory-job-pages.ts`, `scripts/ens/migrate-legacy-batch.ts`, and `scripts/ens/repair-job-page.ts` to classify and repair per-job ENS state. diff --git a/hardhat/package-lock.json b/hardhat/package-lock.json index 625e23e4..97ce1774 100644 --- a/hardhat/package-lock.json +++ b/hardhat/package-lock.json @@ -12,6 +12,7 @@ "@nomicfoundation/hardhat-verify": "^2.1.1", "@openzeppelin/contracts": "4.9.6", "dotenv": "^16.6.1", + "ethers": "^6.15.0", "hardhat": "^2.26.3" } }, @@ -20,8 +21,7 @@ "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@ethereumjs/rlp": { "version": "5.0.2", @@ -544,7 +544,6 @@ "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@noble/hashes": "1.3.2" }, @@ -558,7 +557,6 @@ "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 16" }, @@ -1035,7 +1033,6 @@ "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.19.2" } @@ -1055,8 +1052,7 @@ "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/agent-base": { "version": "6.0.2", @@ -1656,7 +1652,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", @@ -3058,8 +3053,7 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true, - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tsort": { "version": "0.0.1", @@ -3099,8 +3093,7 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/universalify": { "version": "0.1.2", @@ -3190,7 +3183,6 @@ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/scripts/ens/audit-mainnet.ts b/scripts/ens/audit-mainnet.ts index 86751b13..16da0877 100644 --- a/scripts/ens/audit-mainnet.ts +++ b/scripts/ens/audit-mainnet.ts @@ -1,10 +1,7 @@ #!/usr/bin/env node const fs = require('node:fs'); const path = require('node:path'); -const { createRequire } = require('node:module'); - -const requireFromHere = createRequire(__filename); -const { ethers } = requireFromHere('../../hardhat/node_modules/ethers'); +const { ethers } = require('./lib/ethers'); const { CurlJsonRpcProvider } = require('./lib/json_rpc'); const OUTPUT = path.resolve('scripts/ens/output/audit-mainnet.json'); diff --git a/scripts/ens/inventory-job-pages.ts b/scripts/ens/inventory-job-pages.ts index 9d310bd0..dec437bf 100644 --- a/scripts/ens/inventory-job-pages.ts +++ b/scripts/ens/inventory-job-pages.ts @@ -1,10 +1,7 @@ #!/usr/bin/env node const fs = require('node:fs'); const path = require('node:path'); -const { createRequire } = require('node:module'); - -const requireFromHere = createRequire(__filename); -const { ethers } = requireFromHere('../../hardhat/node_modules/ethers'); +const { ethers } = require('./lib/ethers'); const { CurlJsonRpcProvider } = require('./lib/json_rpc'); const OUTPUT = path.resolve('scripts/ens/output/inventory-job-pages.json'); diff --git a/scripts/ens/lib/ethers.js b/scripts/ens/lib/ethers.js new file mode 100644 index 00000000..8ad30231 --- /dev/null +++ b/scripts/ens/lib/ethers.js @@ -0,0 +1,43 @@ +const { createRequire } = require('node:module'); +const path = require('node:path'); + +const requireFromHere = createRequire(__filename); + +function loadEthers() { + const candidates = [ + path.resolve(__dirname, '../../../hardhat/node_modules/ethers'), + 'ethers', + ]; + + for (const candidate of candidates) { + try { + const mod = requireFromHere(candidate); + return mod.ethers || mod; + } catch (error) { + if (candidate === candidates[candidates.length - 1]) throw error; + } + } +} + +const raw = loadEthers(); +const isV6 = typeof raw.ZeroAddress === 'string'; + +function compat() { + if (isV6) return raw; + const utils = raw.utils; + return { + ...raw, + ZeroAddress: raw.constants.AddressZero, + ZeroHash: raw.constants.HashZero, + Contract: raw.Contract, + JsonRpcProvider: raw.providers.JsonRpcProvider, + Interface: utils.Interface, + Wallet: raw.Wallet, + id: utils.id, + namehash: utils.namehash, + ensNormalize: (value) => value.trim().toLowerCase(), + solidityPackedKeccak256: (types, values) => utils.solidityKeccak256(types, values), + }; +} + +module.exports = { ethers: compat() }; diff --git a/scripts/ens/lib/json_rpc.js b/scripts/ens/lib/json_rpc.js index 5860a954..82840507 100644 --- a/scripts/ens/lib/json_rpc.js +++ b/scripts/ens/lib/json_rpc.js @@ -1,8 +1,6 @@ #!/usr/bin/env node const { execFileSync } = require('node:child_process'); -const { createRequire } = require('node:module'); -const requireFromHere = createRequire(__filename); -const { ethers } = requireFromHere('../../../hardhat/node_modules/ethers'); +const { ethers } = require('./ethers'); function toQuantity(value) { return ethers.toBeHex(value); diff --git a/scripts/ens/migrate-legacy-batch.ts b/scripts/ens/migrate-legacy-batch.ts index 78bf43b4..e35c2bb8 100644 --- a/scripts/ens/migrate-legacy-batch.ts +++ b/scripts/ens/migrate-legacy-batch.ts @@ -1,10 +1,7 @@ #!/usr/bin/env node const fs = require('node:fs'); const path = require('node:path'); -const { createRequire } = require('node:module'); - -const requireFromHere = createRequire(__filename); -const { ethers } = requireFromHere('../../hardhat/node_modules/ethers'); +const { ethers } = require('./lib/ethers'); const { CurlJsonRpcProvider } = require('./lib/json_rpc'); const RPC = (process.env.MAINNET_RPC_URL || 'https://ethereum-rpc.publicnode.com').trim(); diff --git a/scripts/ens/output/audit-mainnet.json b/scripts/ens/output/audit-mainnet.json index f2b6c4a8..b278f404 100644 --- a/scripts/ens/output/audit-mainnet.json +++ b/scripts/ens/output/audit-mainnet.json @@ -1,7 +1,7 @@ { - "generatedAt": "2026-03-23T02:55:31.945Z", + "generatedAt": "2026-03-23T12:25:44.492Z", "network": "ethereum-mainnet", - "rpc": "https://ethereum.publicnode.com", + "rpc": "https://ethereum-rpc.publicnode.com", "inputs": { "ensJobPages": "0x97E03F7BFAC116E558A25C8f09aEf09108a2779d", "agiJobManagerPrime": "0xF8fc6572098DDcAc4560E17cA4A683DF30ea993e", @@ -11,9 +11,9 @@ }, "proven": { "latestBlock": { - "number": "0x1792838", - "hash": "0x1f1fdb2e55121b17bffae167a8f4a18ddbf87a0e59b810852df20c97b115b80f", - "timestamp": "0x69c0ab9b" + "number": "0x179334d", + "hash": "0x2a9b42daa495d523f2f25f71ae0015601d17f684780b095f9c4fd3af86243e62", + "timestamp": "0x69c1313f" }, "expectedRootNode": "0xc164c9558a3c429519a9b2eba9f650025731fccc46b3a5664283bcab84f7e690", "handleHook": { diff --git a/scripts/ens/output/inventory-job-pages.json b/scripts/ens/output/inventory-job-pages.json index 10f8f578..1811ac74 100644 --- a/scripts/ens/output/inventory-job-pages.json +++ b/scripts/ens/output/inventory-job-pages.json @@ -1,10 +1,10 @@ { - "generatedAt": "2026-03-23T02:55:37.263Z", - "rpc": "https://ethereum.publicnode.com", + "generatedAt": "2026-03-23T04:25:16.970Z", + "rpc": "https://ethereum-rpc.publicnode.com", "prime": "0xF8fc6572098DDcAc4560E17cA4A683DF30ea993e", "ensJobPages": "0x97E03F7BFAC116E558A25C8f09aEf09108a2779d", "proven": { - "latestBlock": "24717369", + "latestBlock": "24717814", "configurationStatus": null }, "assumed": [], @@ -12,7 +12,7 @@ "summary": { "nextJobId": 0, "scannedJobs": 0, - "maxJobs": 64, + "maxJobs": 32, "truncated": false, "previewOnly": [], "authoritySnapshotted": [], diff --git a/scripts/ens/output/repair-job-page.json b/scripts/ens/output/repair-job-page.json index 11623cab..b5c7e51f 100644 --- a/scripts/ens/output/repair-job-page.json +++ b/scripts/ens/output/repair-job-page.json @@ -1,6 +1,6 @@ { - "generatedAt": "2026-03-23T03:47:05.673Z", - "rpc": "https://ethereum.publicnode.com", + "generatedAt": "2026-03-23T04:25:19.296Z", + "rpc": "https://ethereum-rpc.publicnode.com", "jobId": 0, "exactLabel": "", "execute": false, diff --git a/scripts/ens/phase0-mainnet-snapshot.mjs b/scripts/ens/phase0-mainnet-snapshot.mjs index ecbf3e41..c08c7569 100644 --- a/scripts/ens/phase0-mainnet-snapshot.mjs +++ b/scripts/ens/phase0-mainnet-snapshot.mjs @@ -3,7 +3,7 @@ import path from "node:path"; import { createRequire } from "node:module"; const require = createRequire(import.meta.url); -const { ethers } = require("../../hardhat/node_modules/ethers"); +const { ethers } = require("./lib/ethers"); const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..", ".."); diff --git a/scripts/ens/repair-job-page.ts b/scripts/ens/repair-job-page.ts index 4c8a6694..06d3e1cf 100644 --- a/scripts/ens/repair-job-page.ts +++ b/scripts/ens/repair-job-page.ts @@ -1,10 +1,7 @@ #!/usr/bin/env node const fs = require('node:fs'); const path = require('node:path'); -const { createRequire } = require('node:module'); - -const requireFromHere = createRequire(__filename); -const { ethers } = requireFromHere('../../hardhat/node_modules/ethers'); +const { ethers } = require('./lib/ethers'); const { CurlJsonRpcProvider } = require('./lib/json_rpc'); const RPC = (process.env.MAINNET_RPC_URL || 'https://ethereum-rpc.publicnode.com').trim(); diff --git a/test/ensAuthoritySnapshot.test.js b/test/ensAuthoritySnapshot.test.js index e3d17aa0..76ffdcfd 100644 --- a/test/ensAuthoritySnapshot.test.js +++ b/test/ensAuthoritySnapshot.test.js @@ -75,12 +75,16 @@ contract('ENSJobPages authority snapshots', (accounts) => { await manager.setJob(3, employer, agent, 'ipfs://spec-3', { from: owner }); const status = await pages.configurationStatus(); - assert.equal(status[0], false, 'configuration should not be ready when resolver lacks required interfaces'); + assert.equal(status[0], false, 'configuration should not be ready when resolver lacks a readable text surface'); + assert.equal(status[7], false, 'resolver text support should be explicit'); + assert.equal(status[8], false, 'resolver setText support should be explicit'); + assert.equal(status[9], false, 'resolver setAuthorisation support should be explicit'); const receipt = await manager.callHandleHook(pages.address, 1, 3, { from: owner }); await expectEvent.inTransaction(receipt.tx, pages, 'ENSHookSkipped', { hook: '1', jobId: '3' }); }); + it('exposes machine-readable inspector status for preview/effective/finalization surfaces', async () => { const { manager, pages } = await deployPages(); const inspector = await ENSJobPagesInspector.new({ from: owner });