PR C: wire real NotaryRegistry on Chain 138 (arch step 4) #7

Merged
nsatoshi merged 3 commits from devin/1776875569-notary-chain-wiring into main 2026-04-22 17:11:54 +00:00
Owner

Implements step 4 from the architecture gap-analysis — the ledger-anchor layer (arch §4.5 + §5.7) stops being a mock and starts writing to the deployed NotaryRegistry on Chain 138. Independent of PRs A/B; can land in any order.

What lands

services/notaryChain.ts (new)

ethers-v6 adapter. Minimal ABI (two functions, two events, one view) lifted from contracts/interfaces/INotaryRegistry.sol:

fn purpose
registerPlan(bytes32 planId, Step[] steps, address creator) anchor planHash on-chain
finalizePlan(bytes32 planId, bool success) record commit/abort disposition
getPlan(bytes32) read-side for GET /api/plans/:id/proof (wired later)

Public surface:

  • anchorPlan(plan){ mode: 'chain'|'mock', txHash?, planHash, blockNumber?, contractAddress? }
  • finalizeAnchor(planId, success){ mode, txHash?, receiptHash?, blockNumber? }
  • computePlanHash(plan) and planIdToBytes32(planId) — deterministic helpers so the mock and chain paths publish identical hashes for identical inputs.

A singleton contract/wallet is cached after first use, keyed on contractAddress, and can be reset via __resetForTests().

Graceful mock fallback

If any of CHAIN_138_RPC_URL, NOTARY_REGISTRY_ADDRESS, ORCHESTRATOR_PRIVATE_KEY is unset, both functions return mode: 'mock' and log the reason. Unit tests, CI, and local dev require no extra wiring.

services/notary.ts (refactored)

Still the single entry point for ExecutionCoordinator; now delegates to notaryChain and surfaces the extra on-chain fields (mode, txHash, blockNumber, contractAddress, receiptHash) to callers. If the chain call throws, we log + fall back to mock so a momentary RPC outage cannot take the workflow down.

config/env.ts

CHAIN_138_RPC_URL          url, optional    (e.g. https://rpc.d-bis.org)
CHAIN_138_CHAIN_ID         digits, optional (default 138)
NOTARY_REGISTRY_ADDRESS    0x-40 hex, optional
ORCHESTRATOR_PRIVATE_KEY   0x-64 hex, optional

All four regex-validated. Missing envs do not fail startup.

Tests

tests/unit/notaryChain.test.ts — 6 cases: planIdToBytes32 determinism + collision-resistance, computePlanHash determinism, and the three mock-fallback paths (empty config, partial config, finalizeAnchor).

Verification

$ npx tsc --noEmit     # clean
$ npx jest             # 51 passed, 4 suites (45 pre-existing + 6 new)

Not in this PR

  • No on-chain test: the integration needs a funded signer on Chain 138; that's a manual smoke once an ORCHESTRATOR_PRIVATE_KEY is provisioned.
  • Steps encoding: registerPlan is called with an empty Step[] for now. PR E (SWIFT gateway) introduces stable step IDs, after which we can serialise the real steps.
  • getNotaryProof still returns a deterministic mock — the on-chain read will be wired in PR D alongside the events table (so a single query reconstructs { onChainProof, eventTrail }).

Series order

A → B → C → D → E → F → G → H.

Implements **step 4** from the architecture gap-analysis — the ledger-anchor layer (arch §4.5 + §5.7) stops being a mock and starts writing to the deployed `NotaryRegistry` on Chain 138. Independent of PRs A/B; can land in any order. ## What lands ### `services/notaryChain.ts` (new) ethers-v6 adapter. Minimal ABI (two functions, two events, one view) lifted from `contracts/interfaces/INotaryRegistry.sol`: | fn | purpose | | --- | --- | | `registerPlan(bytes32 planId, Step[] steps, address creator)` | anchor planHash on-chain | | `finalizePlan(bytes32 planId, bool success)` | record commit/abort disposition | | `getPlan(bytes32)` | read-side for `GET /api/plans/:id/proof` (wired later) | Public surface: - `anchorPlan(plan)` → `{ mode: 'chain'|'mock', txHash?, planHash, blockNumber?, contractAddress? }` - `finalizeAnchor(planId, success)` → `{ mode, txHash?, receiptHash?, blockNumber? }` - `computePlanHash(plan)` and `planIdToBytes32(planId)` — deterministic helpers so the mock and chain paths publish identical hashes for identical inputs. A singleton contract/wallet is cached after first use, keyed on `contractAddress`, and can be reset via `__resetForTests()`. ### Graceful mock fallback If **any** of `CHAIN_138_RPC_URL`, `NOTARY_REGISTRY_ADDRESS`, `ORCHESTRATOR_PRIVATE_KEY` is unset, both functions return `mode: 'mock'` and log the reason. Unit tests, CI, and local dev require no extra wiring. ### `services/notary.ts` (refactored) Still the single entry point for `ExecutionCoordinator`; now delegates to `notaryChain` and surfaces the extra on-chain fields (`mode`, `txHash`, `blockNumber`, `contractAddress`, `receiptHash`) to callers. If the chain call throws, we log + fall back to mock so a momentary RPC outage cannot take the workflow down. ### `config/env.ts` ``` CHAIN_138_RPC_URL url, optional (e.g. https://rpc.d-bis.org) CHAIN_138_CHAIN_ID digits, optional (default 138) NOTARY_REGISTRY_ADDRESS 0x-40 hex, optional ORCHESTRATOR_PRIVATE_KEY 0x-64 hex, optional ``` All four regex-validated. Missing envs do **not** fail startup. ### Tests `tests/unit/notaryChain.test.ts` — 6 cases: `planIdToBytes32` determinism + collision-resistance, `computePlanHash` determinism, and the three mock-fallback paths (empty config, partial config, finalizeAnchor). ## Verification ``` $ npx tsc --noEmit # clean $ npx jest # 51 passed, 4 suites (45 pre-existing + 6 new) ``` ## Not in this PR - No on-chain test: the integration needs a funded signer on Chain 138; that's a manual smoke once an ORCHESTRATOR_PRIVATE_KEY is provisioned. - Steps encoding: `registerPlan` is called with an empty `Step[]` for now. PR E (SWIFT gateway) introduces stable step IDs, after which we can serialise the real steps. - `getNotaryProof` still returns a deterministic mock — the on-chain read will be wired in PR D alongside the events table (so a single query reconstructs `{ onChainProof, eventTrail }`). ## Series order A → B → **C** → D → E → F → G → H.
nsatoshi added 3 commits 2026-04-22 16:33:36 +00:00
PR A: 12-state transaction machine + issueInstrument step + SoD matrix
Some checks failed
Code Quality / SonarQube Analysis (pull_request) Failing after 23s
Code Quality / Code Quality Checks (pull_request) Failing after 11s
Security Scan / Dependency Vulnerability Scan (pull_request) Failing after 4s
Security Scan / OWASP ZAP Scan (pull_request) Failing after 5s
b24a4df983
Architecture note steps 1, 2, 10 (data model).

- types/transactionState.ts: 12 states, allowed-transition table, SoD matrix
- types/plan.ts: add InstrumentTerms + 'issueInstrument' PlanStep type
- services/planValidation.ts: validate SBLC step (BIC, ISO-4217, sha256,
  YYYY-MM-DD expiry, >0 amount)
- services/stateMachine.ts: transition() enforces legality + SoD + appends
  to transaction_state_transitions
- db/migrations/002: plans.transaction_state (CHECK) +
  transaction_state_transitions append-only table
- tests/unit: 13 + 8 unit tests (31 total, all pass)

No behaviour change yet: coordinator still uses legacy status field.
PRs B-G will migrate execution paths onto the new machine.
- services/exceptionManager.ts: single taxonomy (timing/data/control/
  business/system) with §12 codes, deterministic route() table, and
  handle() dispatch to retry/DLQ/escalate
- services/execution.ts: refactor executePlan to drive the full 12-state
  machine (DRAFT -> INITIATED -> ... -> VALIDATING -> COMMITTED -> CLOSED)
  via stateMachine.transition(), with a new validatePhase() that
  reconciles DLT tx hash + bank message id + per-step amounts before
  COMMIT; SoD-gated edges use distinct synthetic actors by default
- api/plans.ts + index.ts: GET /api/plans/:planId/state returning
  current transaction_state + full audit trail of transitions
- tests/unit/exceptionManager.test.ts: 14 tests for classification +
  routing matrix

59 tests pass. tsc clean.
PR C: wire real NotaryRegistry contract on Chain 138 (arch step 4)
Some checks failed
Code Quality / SonarQube Analysis (pull_request) Failing after 20s
Code Quality / Code Quality Checks (pull_request) Failing after 8s
Security Scan / Dependency Vulnerability Scan (pull_request) Failing after 3s
Security Scan / OWASP ZAP Scan (pull_request) Failing after 4s
5bd6a200c3
- services/notaryChain.ts: new ethers-v6 adapter speaking to the
  deployed NotaryRegistry.sol via CHAIN_138_RPC_URL +
  NOTARY_REGISTRY_ADDRESS + ORCHESTRATOR_PRIVATE_KEY. Exposes
  anchorPlan(plan) -> { mode, txHash, planHash, blockNumber } and
  finalizeAnchor(planId, success) -> { mode, txHash, receiptHash }
  with deterministic mock fallback when envs are absent.
- services/notary.ts: refactored to delegate to notaryChain; preserves
  the prior signature and returns extra on-chain fields (mode, txHash,
  blockNumber, contractAddress) when the anchor lands.
- config/env.ts: add CHAIN_138_RPC_URL, CHAIN_138_CHAIN_ID,
  NOTARY_REGISTRY_ADDRESS, ORCHESTRATOR_PRIVATE_KEY (all optional,
  validated via regex where applicable).
- package.json: add ethers@^6.11.0 dependency.
- tests/unit/notaryChain.test.ts: 6 tests covering deterministic
  hashing helpers and the mock fallback path.

tsc clean. 51 tests pass (45 pre-existing + 6 new).
nsatoshi merged commit 3e1fb9ef7e into main 2026-04-22 17:11:54 +00:00
nsatoshi deleted branch devin/1776875569-notary-chain-wiring 2026-04-22 17:11:56 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: d-bis/CurrenciCombo#7