On-chain attestation — Neon Law Node (Solana)

Status: scaffolded, not shipped. The durable local record and the workflow seam are implemented and tested; the on-chain write itself is deferred behind a trait. The Node product page says so plainly, and the code cannot lie about it — see Why it can't claim a false record.

Neon Law Node is an attorney attestation recorded on-chain: a licensed attorney confirms a fact from the records a client provides, and a hash of the signed attestation is written to Solana, binding the firm's wallet, the client's wallet, and that hash. Solana is the chain because the workspace is Rust top-to-bottom — Solana programs are written in Rust and Anchor is a framework of Rust macros — so the same workspace speaks to the chain natively. The marketing copy lives in web/content/marketing/node.md; the binding engagement letter is notation_templates/onboarding/retainer_node.md.

What is built

The vertical is implemented end-to-end except the chain write, mirroring how cloud::StorageService keeps GCS out of the generic layer:

Why it can't claim a false record

The honesty is enforced by code, not copy. dispatch_onchain_record sets status = recorded only when the attestor returns a real RecordedTx; NullAttestor returns None, so the row stays pending with no transaction. And attestor_from_env makes the solana backend error at startup until the real implementation lands — a deployer can never silently believe attestations are going on-chain when they are not. This is why the step is deliberately not yet wired into the binding onboarding__retainer_node workflow: a binding retainer must not route through a step that, in its current default, records nothing.

What is deferred

Two pieces of code and four decisions. The code is straightforward; the decisions are the real gate and want a council before any keypair touches production.

The code

1. SolanaAttestor — an impl Attestor holding an RPC client, the program id, and the firm signer. It builds the record_attestation instruction, submits it, waits for the chosen commitment, and returns the signature + PDA.

2. The Anchor program — one instruction, record_attestation, that initializes a Program Derived Address seeded by the notation id (init, seeds = [b"attestation", notation_id], bump, payer = firm), storing:

struct Attestation { firm: Pubkey, client: Pubkey, sha256: [u8; 32], recorded_at: i64 }

The PDA is also the exactly-once key: a replayed submit hits "account already in use", which the attestor treats as success — the chain itself dedupes, complementing the journaled ctx.run.

3. The workflow edge — the one-line YAML change in retainer_node.md, routing the signature into the new step:

sent_for_signature__pending:
  signature_received: onchain__record_attestation
  signature_declined: END
onchain__record_attestation:
  attestation_recorded: END
  attestation_failed: staff_review

This ripples into the retainer / e-signature test suite (the tests that assert signature_received → END), so it lands with the SolanaAttestor and its test updates, not before.

The decisions (not code)

"It's written in Rust" chose the SDK; it did not answer any of these. Each gates production:

Configuration

NAVIGATOR_ONCHAIN_BACKEND selects the backend (null default). When the SolanaAttestor ships it reads SOLANA_RPC_URL, SOLANA_PROGRAM_ID, and a KMS reference for the signer. See .env.example for the committed contract.

Pointers