Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a5a03e2
chore: scaffold the EFS SDK monorepo
JamesCarnley Jun 10, 2026
7f0f5b7
docs(adr): note the lightweight MADR-style lineage
JamesCarnley Jun 10, 2026
c8e5a19
chore: adopt @efs npm scope; trim ADR system per validation
JamesCarnley Jun 10, 2026
885a0ad
docs: add docs/specs/ — plain-language "how it works" layer
JamesCarnley Jun 10, 2026
7994df5
ci: fix scope rename in CI; add OIDC release workflow (ADR-0004)
JamesCarnley Jun 10, 2026
117b1b4
ci: fix Solidity forge-std install and changeset gate
JamesCarnley Jun 11, 2026
daa503a
ci: harden workflows per review
JamesCarnley Jun 11, 2026
eadb898
fix(solidity): forge fmt clean + drop stub warnings
JamesCarnley Jun 11, 2026
bd6f0a3
ci/sdk: address Codex review — release build scope + stale version
JamesCarnley Jun 11, 2026
ba5c8b1
adr: ADR-0005 — per-chain deployments registry; SDK is a client not a…
JamesCarnley Jun 11, 2026
d1e88d3
fix: address SDK-relevant findings from holistic review (cheap defects)
JamesCarnley Jun 11, 2026
cf3025e
plan: beta-slice implementation spec (review-hardened)
JamesCarnley Jun 11, 2026
85c7d3d
ci: unblock Changesets version PR + make solidity tests self-bootstra…
JamesCarnley Jun 11, 2026
2c88bc2
feat(content): contentHash = bare SHA-256 (ADR-0006) + real hashing m…
JamesCarnley Jun 11, 2026
39653f6
style: biome-format verifyContent signature (CI lint fix)
JamesCarnley Jun 11, 2026
e71548b
ci: disable auto-publish until launch (Codex P1)
JamesCarnley Jun 11, 2026
5e92a8d
feat(sdk): namespaced client (F) + EAS layer, lenses, deployments, er…
JamesCarnley Jun 11, 2026
c70a66f
fix(sdk): async fs stubs + honest integrity-check docs (review)
JamesCarnley Jun 11, 2026
145e590
docs: fix package README quickstart to namespaced API (Codex P2)
JamesCarnley Jun 11, 2026
9e5467f
fix(eas): forward resolver value as msg.value in attest builders (Cod…
JamesCarnley Jun 11, 2026
4017c17
refactor(sdk): foundation hardening batch 1 — types, write-gating, seams
JamesCarnley Jun 11, 2026
82d5b03
docs: quickstart uses the actual ReadOptions key `lens` (Codex P2)
JamesCarnley Jun 11, 2026
f9733fc
chore(sdk): foundation hardening batch 2 — exports, attw/publint, ADRs
JamesCarnley Jun 11, 2026
cdd250f
docs: standards-foundation spec (7-domain EIP/ERC/CAIP research pass)
JamesCarnley Jun 11, 2026
1ca8d9e
fix(solidity): exports map exposes the documented src/*.sol import (C…
JamesCarnley Jun 11, 2026
3443be8
feat(sdk): EIP-1193 provider boundary (standard at the edge, viem ins…
JamesCarnley Jun 11, 2026
69239b1
fix(solidity): _efsPinFile locks the emit happy-path shape (Codex P2)
JamesCarnley Jun 11, 2026
3e1233e
docs: future-proofing doctrine (9-domain research pass 2)
JamesCarnley Jun 11, 2026
97230fe
docs: recover dropped pass-2 findings (completeness sweep + sim seam)
JamesCarnley Jun 11, 2026
d4e413c
review: apply verified API + standards review (40 findings)
JamesCarnley Jun 11, 2026
449c286
feat: build freeze-independent foundation (fetch engine, classifier, …
JamesCarnley Jun 11, 2026
ae1d1c0
fix(ci): scope noNonNullAssertion off for tests, keep src strict
JamesCarnley Jun 11, 2026
78a6a6c
fix(mirror): block canonical hex IPv4-mapped IPv6 in SSRF guard (P1)
JamesCarnley Jun 11, 2026
a0c0d83
fix(mirror): re-check redirect targets in SSRF guard (P1)
JamesCarnley Jun 11, 2026
59253c1
fix(mirror): trailing-dot host bypass (P1) + data: URI size cap (P2)
JamesCarnley Jun 11, 2026
2fa8bf9
harden(mirror): IPv6 transition SSRF, decompression bomb, data: decod…
JamesCarnley Jun 11, 2026
db195ad
harden(mirror): reject gateway path traversal in ipfs/arweave subpath…
JamesCarnley Jun 11, 2026
335762d
harden(mirror): require / boundary in gateway namespace check (P2)
JamesCarnley Jun 11, 2026
0e6a859
feat: build on merged on-chain tag-exclusion filter + folder Overview…
JamesCarnley Jun 15, 2026
310ef5f
chore(size): raise budget to 10 kB for the view ABI + filter/Overview…
JamesCarnley Jun 15, 2026
8481394
fix(mirror,errors): UTF-8 data: cap + walk cause chain for error code…
JamesCarnley Jun 15, 2026
f4f9bb5
fix(mirror): exclude base64 padding/whitespace from data: cap prechec…
JamesCarnley Jun 15, 2026
fa56b11
fix(mirror,eas): byte-wise data: octet decode + EAS error fragments (…
JamesCarnley Jun 15, 2026
be24b0c
fix(mirror): percent-decode base64 data: bodies before decoding (P2)
JamesCarnley Jun 15, 2026
28a0bd8
fix(mirror): bound-aware data: octet decode — enforce cap DURING deco…
JamesCarnley Jun 15, 2026
1f0541f
fix(mirror): size-check percent-encoded base64 before materializing (P2)
JamesCarnley Jun 15, 2026
47dd93b
fix(mirror): trim media type before ;base64 test + surrogate-safe flush
JamesCarnley Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
4 changes: 4 additions & 0 deletions .changeset/initial-scaffold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Initial monorepo scaffold — structure, toolchain, ADRs, and docs. No package release (both packages are unpublished `0.0.0`); this empty changeset satisfies the CI gate for a no-release PR.
90 changes: 90 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: CI

on:
push:
branches: [main]
pull_request:

# CI only reads the repo — never needs write.
permissions:
contents: read

# Cancel superseded runs on the same ref.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
ts:
name: TypeScript (build, typecheck, test, lint)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
# --ignore-scripts neutralizes malicious dependency postinstall/preinstall
# scripts — the dominant supply-chain attack on a wallet-adjacent SDK.
# It only suppresses *dependency* lifecycle scripts; explicit `pnpm run`
# pre/post hooks (e.g. the Solidity forge-std bootstrap) still run.
- run: pnpm install --frozen-lockfile --ignore-scripts
- run: pnpm --filter @efs/sdk build
# Guard the dual ESM/CJS `exports` map: attw catches type masquerading /
# resolution bugs, publint catches malformed package metadata.
# node16 profile: the package requires Node >=20 and ships an `exports`
# map, so legacy node10 resolution (which ignores `exports`, breaking
# subpath entries) is an explicit non-target.
- run: pnpm --filter @efs/sdk exec attw --pack --profile node16
- run: pnpm --filter @efs/sdk exec publint
- run: pnpm --filter @efs/sdk typecheck
- run: pnpm --filter @efs/sdk test
# Bundle-size budget on the @efs/sdk ESM entry (viem external). Fails the
# build if the gzipped surface grows past the budget in .size-limit.json.
- run: pnpm --filter @efs/sdk size
# Dead-code / unused-dependency report. Report-only (|| true) for now — the
# findings are follow-ups, not a merge gate, while the API surface settles.
- run: pnpm --filter @efs/sdk knip || true
- run: pnpm lint

solidity:
name: Solidity (build, test, fmt)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: foundry-rs/foundry-toolchain@v1
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile --ignore-scripts
# Use the package scripts so CI and a contributor's `pnpm test` run the same
# path; the build/test pre-hooks bootstrap forge-std (test-only, not shipped).
# --ignore-scripts above only suppresses *dependency* install scripts, so
# these explicit `pnpm run` pre/post hooks still execute.
- run: pnpm --filter @efs/solidity build
- run: pnpm --filter @efs/solidity test
- run: pnpm --filter @efs/solidity fmt:check

changeset:
name: Changeset present
# Skip the gate for Changesets' own "Version Packages" PR, which consumes
# (deletes) the changeset files — it legitimately has none left.
if: github.event_name == 'pull_request' && github.head_ref != 'changeset-release/main'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile --ignore-scripts
- run: pnpm changeset status --since=origin/main
Comment thread
JamesCarnley marked this conversation as resolved.
44 changes: 44 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Release

on:
push:
branches: [main]

concurrency: release-${{ github.ref }}

permissions:
contents: write # create version PR, tags, GitHub releases
pull-requests: write # open the Changesets "Version Packages" PR
id-token: write # OIDC — npm Trusted Publishing (no NPM_TOKEN secret)

jobs:
release:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22 # Trusted Publishing needs Node >= 22.14 / npm >= 11.5.1
cache: pnpm
registry-url: https://registry.npmjs.org
# --ignore-scripts: block dependency install scripts on the publish path too.
- run: pnpm install --frozen-lockfile --ignore-scripts
- run: npm install -g npm@11 # pinned major; needs npm >= 11.5.1 for OIDC publishing
# Only the TS package needs a build artifact to publish; @efs/solidity ships
# .sol source as-is, so the release path needs no Foundry. (CI compile-checks
# the Solidity package on every PR before merge.)
- run: pnpm --filter @efs/sdk build
# PUBLISHING IS INTENTIONALLY DISABLED UNTIL LAUNCH. The packages are pre-1.0,
# unpublished 0.0.0, and the @efs npm org isn't created yet — auto-publishing
# now would push a scaffold. With no `publish:` input, changesets/action only
# manages the "Version Packages" PR; it never publishes.
#
# TO ENABLE AT LAUNCH: add `with: { publish: pnpm release }` below, and
# configure a Trusted Publisher PER PACKAGE on npmjs.com (OIDC, no token):
# package settings -> Trusted Publisher -> repo "efs-project/sdk",
# workflow ".github/workflows/release.yml". Repo must stay public.
- uses: changesets/action@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29 changes: 29 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Dependencies
node_modules/
.pnpm-store/

# TypeScript / build output
dist/
*.tsbuildinfo

# Turbo
.turbo/

# Foundry (packages/solidity) — build artifacts, never shipped or tracked
out/
cache/
broadcast/
packages/solidity/out/
packages/solidity/cache/
packages/solidity/lib/

# Env / secrets
.env
.env.*
!.env.example

# Editor / OS
.DS_Store
.idea/
.vscode/
*.log
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
37 changes: 37 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# AGENTS.md

The **EFS SDK** — the developer-facing SDK for the Ethereum File System (an on-chain filesystem on EAS attestations). This repo ships two packages: a TypeScript SDK and a compile-in Solidity SDK. Pre-1.0, pre-launch — breaking changes are fine for now; good design and future-proofing of the *public surface* is what matters.

This is the **upgradeable** layer of EFS (vs. the immutable contracts). Ship the simple version, observe, revise.

## Read on init

**If your tool does not auto-load `@`-imported files, read these before starting any task:**

- **[docs/specs/overview.md](./docs/specs/overview.md)** — plain-language *how the SDK works* (the model). Start here to understand behaviour.
- **[docs/adr/README.md](./docs/adr/README.md)** — the ADR system and the **boundary rule** (SDK ADR vs. planning-vault design). Required before writing a decision.
- **[README.md](./README.md)** — what the two packages are and how they're consumed.

Doc layers (don't duplicate): **specs** = how it works now · **ADRs** = why we chose it · **planning vault** = cross-cutting design. See [docs/specs/README.md](./docs/specs/README.md).

## The two packages

- **`packages/sdk`** → `@efs/sdk` — TypeScript, off-chain reads/writes, viem-native (ADR-0002).
- **`packages/solidity`** → `@efs/solidity` — a compile-in Solidity library (ADR-0003); your contract stays the attester.

## Cross-repo coordination — the planning vault

The cross-cutting SDK architecture lives in the **planning vault**, not here: `planning/Designs/sdk-architecture.md` (the "what + why"), plus `planning/Designs/sdk-minimal-clicks.md`. This repo's ADRs implement *slices* of that design.

**Boundary rule (see [docs/adr/README.md](./docs/adr/README.md)):**
- SDK-only decision (package layout, deps, error model, API shape) → an **SDK ADR** here.
- A decision that changes EFS *architecture* or touches the **contracts**/**client** repos → a **planning-vault design**, not an SDK ADR.
- SDK code decisions never go in `planning/Decisions.md` (that's for cross-repo coordination only).

The sibling repos: protocol contracts (`efs-project/contracts`) and the production client (`efs-project/client`).

## Conventions

- **ADRs:** mirror the contracts repo's format, independent numbering from `0001`, lighter discipline (no freeze ceremony). Supersede-don't-edit accepted ADRs.
- **Commits:** conventional style (`feat:`, `fix:`, `docs:`, `adr:`, `chore:`).
- **Quality:** `pnpm build && pnpm test && pnpm typecheck && pnpm lint` before a PR. Every change that touches a published package needs a Changeset (`pnpm changeset`).
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# CLAUDE.md

This repo uses **AGENTS.md** as the canonical entrypoint for all AI agents. The files below are auto-loaded into context at session start:

@AGENTS.md
@docs/adr/README.md
43 changes: 43 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Contributing

## Setup

```bash
pnpm install
```

Requires **pnpm 9+**, **Node 20+**, and **[Foundry](https://book.getfoundry.sh/)** (for `packages/solidity`).

## Commands

```bash
pnpm build # build both packages (turbo)
pnpm test # vitest (sdk) + forge test (solidity)
pnpm typecheck # tsc --noEmit
pnpm lint # biome ci
pnpm format # biome format --write
```

Per package: `pnpm --filter @efs/sdk test`, `pnpm --filter @efs/solidity build`, etc.

## Before you open a PR

1. `pnpm build && pnpm test && pnpm typecheck && pnpm lint` are green.
2. If you changed a **published package**, add a Changeset: `pnpm changeset` (pick the bump; write a one-line consumer-facing summary).
3. If you made a **decision** (a choice with alternatives), write an ADR — see below.

## Decisions → ADRs

Read [`docs/adr/README.md`](./docs/adr/README.md). The short version:

- A decision **scoped to the SDK** (package layout, deps, error model, public API shape) → a new ADR in `docs/adr/`, numbered from the next free `NNNN`, using `_template.md`.
- A decision that changes EFS **architecture** or touches the **contracts**/**client** repos → it belongs in the **planning vault** (`planning/Designs/`), not here.
- Accepted ADRs are immutable — to change one, write a new ADR and mark the old `Superseded by ADR-NNNN`.

The SDK is the upgradeable layer, so the ADR bar is light: same format as the contracts repo, no freeze ceremony.

## Style

- TypeScript: Biome (`pnpm format`); `strict` tsconfig; typed errors (extend `EfsError`), never raw RPC strings.
- Solidity: `forge fmt` (solhint planned).
- Commits: conventional (`feat:`, `fix:`, `docs:`, `adr:`, `chore:`).
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# EFS SDK

The developer SDK for the **Ethereum File System (EFS)** — an on-chain filesystem built on [EAS](https://attest.org) attestations.

> **Status: scaffold.** Repo structure, toolchain, and public API shapes are in place; implementations land next. How it works: [`docs/specs/overview.md`](./docs/specs/overview.md). Decisions: [`docs/adr/`](./docs/adr). (Cross-cutting design rationale lives in the EFS planning vault, internal.)

## Two packages, two audiences

| Package | npm | For |
|---|---|---|
| [`packages/sdk`](./packages/sdk) | `@efs/sdk` | **TypeScript** — apps, scripts, agents reading/writing EFS off-chain. viem-native. |
| [`packages/solidity`](./packages/solidity) | `@efs/solidity` | **Solidity** — a compile-in library so your *own contract* can read/write EFS. |

```bash
npm i @efs/sdk viem # TypeScript SDK
npm i @efs/solidity # Solidity library (compile-in)
```

## Repo layout

```
packages/
sdk/ TypeScript SDK (tsup, viem, vitest)
solidity/ Solidity library (Foundry, ships .sol source)
examples/ runnable consumers (foundry / hardhat / ts)
docs/adr/ architecture decision records (this repo's decisions)
```

The cross-cutting design lives in the **planning vault**; this repo holds the code and its per-repo ADRs. See [AGENTS.md](./AGENTS.md) for the boundary rule.

## Develop

```bash
pnpm install
pnpm build # turbo: builds both packages
pnpm test # turbo: vitest + forge test
pnpm typecheck
pnpm lint # biome
```

Requires **pnpm 9+**, **Node 20+**, and **Foundry** (for the Solidity package).

## Contributing

See [CONTRIBUTING.md](./CONTRIBUTING.md). Every change to a published package needs a Changeset.

## License

[MIT](./LICENSE)
29 changes: 29 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
"files": { "ignore": ["dist", "out", "cache", "node_modules", "**/*.sol"] },
"organizeImports": { "enabled": true },
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"linter": {
"enabled": true,
"rules": { "recommended": true }
},
"javascript": {
"formatter": { "quoteStyle": "single", "semicolons": "asNeeded" }
},
"overrides": [
{
"include": ["**/test/**", "**/*.test.ts"],
"linter": {
"rules": {
"style": { "noNonNullAssertion": "off" }
}
}
}
]
}
44 changes: 44 additions & 0 deletions docs/adr/0001-monorepo-layout-and-toolchain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# ADR-0001: Monorepo layout, packages & toolchain

**Status:** Accepted
**Date:** 2026-06-10
**Related:** planning/Designs/sdk-architecture.md (Q1: both SDKs in one repo)

## Context

The EFS SDK ships two deliverables from one repo (`github.com/efs-project/sdk`): a **TypeScript SDK** (npm, off-chain reads/writes) and an **on-chain Solidity SDK** (a compile-in library). The planning design resolved Q1 — both live here because distribution, not deployment, is the deciding factor: the Solidity library is `npm install`-ed and compiled into a dev's own contract, exactly like `@openzeppelin/contracts`.

We need a layout that is clean for us (builders) and obvious for external devs, with a publishing story that works for both audiences.

## Decision

A **pnpm + Turborepo + Changesets** monorepo with two independently-versioned packages:

```
sdk/
├── packages/
│ ├── sdk/ → npm @efs/sdk (TypeScript, off-chain)
│ └── solidity/ → npm @efs/solidity (Solidity, compile-in source)
├── examples/ (foundry-consumer, hardhat-consumer, ts-quickstart)
├── docs/adr/ (this system)
├── pnpm-workspace.yaml, turbo.json, tsconfig.base.json, biome.json, .changeset/
```

- **Workspace manager: pnpm.** De-facto standard for 2025–2026 TS+Solidity monorepos (viem, wagmi); strict `node_modules` avoids phantom-dependency bugs. We override the sibling `contracts` repo's yarn/scaffold-eth convention — inheriting yarn isn't worth it here.
- **Task runner: Turborepo.** Caches build/test/typecheck across packages.
- **Two packages, versioned independently via Changesets.** The Solidity ABI and the TS API move on different cadences; lockstep would force no-op bumps and dishonest changelogs.
- **npm scope `@efs`** (selected 2026-06-10 after confirming the scope has no published packages on the npm registry; pending the org being claimed via `npm org create efs` before first publish). The GitHub org stays `efs-project`; only the npm scope is the shorter `@efs`.
- **Package name `@efs/solidity`, not `…/contracts`** — to avoid conceptual collision with the core protocol repo (`efs-project/contracts`). This SDK is a *library you compile in*, not the deployed protocol.

## Consequences

- External devs run `npm i @efs/sdk` (TS) or `npm i @efs/solidity` (Solidity source + remap). One repo, two clear install paths.
- Changesets gates every PR on a stated version intent; CI opens a "Version Packages" PR and publishes on merge.
- The package names cross the npm boundary, so renaming after external adoption is a breaking change — hence settling the `@efs` scope pre-publish.
- Toolchain specifics (bundler, test runner, lint) are recorded in ADR-0002+ and the package configs; they are Ephemeral and revisable.

## Alternatives considered

- **Single package** holding both TS and Solidity — rejected: the two have different consumers, toolchains, and release cadences; one version line couples them artificially.
- **yarn workspaces** (for parity with `contracts`) — rejected: consistency with a legacy scaffold doesn't outweigh pnpm's correctness and ecosystem momentum.
- **Two separate repos** — rejected by the design's Q1: they share docs, examples, and a coordinated story; one repo keeps them in sync.
Loading
Loading