From 393a6b383f86571d1eb0e3386b0047dda99bac2d Mon Sep 17 00:00:00 2001 From: Ty J Everett Date: Thu, 30 Jul 2026 10:55:47 -0700 Subject: [PATCH 1/2] chore(governance): centralize contributor and agent policy --- .github/CODEOWNERS | 7 + .github/ISSUE_TEMPLATE/bug_report.yml | 81 ++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/proposal.yml | 59 + .github/SECURITY.md | 2 +- .github/dependabot.yml | 2 +- .github/pull_request_template.md | 14 + .github/workflows/ci.yml | 3 + AGENTS.md | 139 +++ CLAUDE.md | 45 +- CODE_OF_CONDUCT.md | 40 + CONFORMANCE_AND_DOCS_AUDIT.md | 6 +- CONTRIBUTING.md | 258 +++++ README.md | 15 +- conformance/runner/AGENTS.md | 10 + conformance/runner/ts/AGENTS.md | 10 + docs-site/AGENTS.md | 10 + docs/about/contributing.md | 471 +------- docs/about/doc-agent.md | 22 +- docs/packages/helpers/templates.md | 2 +- docs/packages/sdk/bsv-sdk.md | 2 +- docs/packages/wallet/wallet-toolbox-client.md | 2 +- docs/packages/wallet/wallet-toolbox-mobile.md | 2 +- docs/packages/wallet/wallet-toolbox.md | 2 +- docs/reference/package-api-migrations.md | 20 +- docs/reference/release-operations.md | 3 +- docs/reference/repository-health.md | 27 +- docs/reference/stack-facts.md | 10 +- docs/specs/index.md | 4 +- governance/contributor-policy.json | 171 +++ governance/documentation-policy.json | 8 +- governance/package-release-notes.json | 10 +- governance/repository-health/baselines.json | 10 +- infra/chaintracks-server/AGENTS.md | 10 + .../.github/workflows/build-and-push.yml | 51 - .../.github/workflows/deploy.yaml.example | 64 -- .../.github/workflows/image.yaml | 66 -- infra/message-box-server/AGENTS.md | 119 +- .../.github/workflows/image.yaml | 66 -- infra/overlay-server/AGENTS.md | 204 +--- infra/uhrp-server-basic/AGENTS.md | 75 +- .../.github/dependabot.yml | 7 - .../.github/workflows/build.yaml | 66 -- .../.github/workflows/deploy.yaml | 140 --- .../.github/workflows/setup.yaml | 248 ---- infra/uhrp-server-cloud-bucket/AGENTS.md | 100 +- .../wab/.github/workflows/deploy.yaml.example | 71 -- infra/wab/AGENTS.md | 167 +-- .../.github/workflows/build-and-push.yml | 51 - .../wallet-infra/.github/workflows/build.yaml | 66 -- infra/wallet-infra/AGENTS.md | 95 +- package.json | 6 +- .../amountinator/.github/workflows/push.yaml | 73 -- packages/helpers/amountinator/AGENTS.md | 129 +-- .../.github/ISSUE_TEMPLATE/bug_report.md | 40 - .../.github/ISSUE_TEMPLATE/discussion.md | 24 - .../.github/pull_request_template.md | 25 - .../.github/workflows/push.yaml | 72 -- packages/helpers/bsv-wallet-helper/AGENTS.md | 208 +--- packages/helpers/create-bsv-app/AGENTS.md | 10 + .../did-client/.github/workflows/push.yaml | 73 -- packages/helpers/did-client/AGENTS.md | 162 +-- packages/helpers/did/AGENTS.md | 10 + packages/helpers/fund-wallet/AGENTS.md | 110 +- .../.github/ISSUE_TEMPLATE/bug_report.md | 40 - .../.github/ISSUE_TEMPLATE/discussion.md | 24 - .../simple/.github/pull_request_template.md | 25 - .../simple/.github/workflows/push.yaml | 77 -- packages/helpers/simple/AGENTS.md | 1019 +---------------- .../ts-templates/.github/workflows/push.yaml | 73 -- packages/helpers/ts-templates/AGENTS.md | 149 +-- packages/helpers/ts-templates/CHANGELOG.md | 2 + packages/helpers/ts-templates/CONTRIBUTING.md | 85 -- packages/helpers/ts-templates/README.md | 13 +- packages/helpers/ts-templates/package.json | 2 +- .../.github/workflows/push.yaml | 73 -- .../messaging/authsocket-client/AGENTS.md | 100 +- .../authsocket/.github/workflows/push.yaml | 73 -- packages/messaging/authsocket/AGENTS.md | 118 +- .../messaging/message-box-client/AGENTS.md | 107 +- .../.github/ISSUE_TEMPLATE/bug_report.md | 40 - .../.github/ISSUE_TEMPLATE/discussion.md | 24 - .../.github/pull_request_template.md | 23 - .../ts-paymail/.github/workflows/push.yaml | 73 -- packages/messaging/ts-paymail/AGENTS.md | 105 +- .../messaging/ts-paymail/CODE_OF_CONDUCT.md | 30 - packages/messaging/ts-paymail/CONTRIBUTING.md | 85 -- .../ts-paymail/docs/examples/AGENTS.md | 10 + .../402-pay/.github/workflows/push.yaml | 75 -- packages/middleware/402-pay/AGENTS.md | 185 +-- .../.github/workflows/push.yaml | 73 -- .../auth-express-middleware/AGENTS.md | 97 +- packages/middleware/auth/AGENTS.md | 10 + .../.github/workflows/push.yaml | 73 -- .../payment-express-middleware/AGENTS.md | 91 +- .../.github/ISSUE_TEMPLATE/bug_report.md | 40 - .../.github/ISSUE_TEMPLATE/discussion.md | 24 - .../ts-p2p/.github/pull_request_template.md | 23 - .../ts-p2p/.github/workflows/push.yaml | 73 -- packages/network/ts-p2p/AGENTS.md | 179 +-- packages/network/ts-p2p/CODE_OF_CONDUCT.md | 30 - packages/network/ts-p2p/CONTRIBUTING.md | 85 -- packages/overlays/btms-backend/AGENTS.md | 163 +-- .../gasp-core/.github/workflows/push.yaml | 73 -- packages/overlays/gasp-core/AGENTS.md | 269 +---- .../.github/workflows/push.yaml | 72 -- .../overlay-discovery-services/AGENTS.md | 170 +-- .../.github/ISSUE_TEMPLATE/bug_report.md | 40 - .../.github/ISSUE_TEMPLATE/discussion.md | 24 - .../.github/pull_request_template.md | 25 - .../.github/workflows/push.yaml | 75 -- packages/overlays/overlay-express/AGENTS.md | 182 +-- .../.github/ISSUE_TEMPLATE/bug_report.md | 40 - .../.github/ISSUE_TEMPLATE/discussion.md | 24 - .../overlay/.github/pull_request_template.md | 25 - .../overlay/.github/workflows/push.yaml | 73 -- packages/overlays/overlay/AGENTS.md | 164 +-- packages/overlays/overlay/CONTRIBUTING.md | 85 -- packages/overlays/topics/AGENTS.md | 237 +--- packages/sdk/AGENTS.md | 289 +---- packages/sdk/BENCHMARK.md | 2 +- packages/sdk/CHANGELOG.md | 2 + packages/sdk/CONTRIBUTING.md | 85 -- packages/sdk/README.md | 3 +- packages/sdk/package.json | 2 +- packages/verifast/AGENTS.md | 10 + .../wallet/btms-permission-module/AGENTS.md | 229 +--- packages/wallet/btms/AGENTS.md | 242 +--- packages/wallet/ts-wallet-relay/AGENTS.md | 330 +----- .../wallet/wallet-toolbox-examples/AGENTS.md | 10 + .../wallet/wallet-toolbox-examples/README.md | 3 +- .../.github/ISSUE_TEMPLATE/bug_report.md | 40 - .../.github/ISSUE_TEMPLATE/discussion.md | 24 - .../.github/pull_request_template.md | 22 - .../.github/workflows/push.yaml | 146 --- packages/wallet/wallet-toolbox/AGENTS.md | 307 +---- packages/wallet/wallet-toolbox/CHANGELOG.md | 3 + .../wallet/wallet-toolbox/CONTRIBUTING.md | 89 -- packages/wallet/wallet-toolbox/README.md | 4 +- .../wallet/wallet-toolbox/client/AGENTS.md | 10 + .../wallet/wallet-toolbox/client/package.json | 2 +- .../wallet/wallet-toolbox/mobile/AGENTS.md | 10 + .../wallet/wallet-toolbox/mobile/package.json | 2 +- packages/wallet/wallet-toolbox/package.json | 2 +- .../src/storage/__test/StorageIdb.test.ts | 2 +- plans/GOVERNANCE.md | 6 +- plans/HUMAN_QUESTION.md | 5 +- plans/README.md | 8 +- plans/RELIABILITY.md | 6 +- scripts/container-supply-chain.test.mjs | 4 +- scripts/contributor-policy.mjs | 279 +++++ scripts/contributor-policy.test.mjs | 141 +++ specs/reliability/README.md | 7 +- 153 files changed, 1766 insertions(+), 9809 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/proposal.yml create mode 100644 AGENTS.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 conformance/runner/AGENTS.md create mode 100644 conformance/runner/ts/AGENTS.md create mode 100644 docs-site/AGENTS.md create mode 100644 governance/contributor-policy.json create mode 100644 infra/chaintracks-server/AGENTS.md delete mode 100644 infra/message-box-server/.github/workflows/build-and-push.yml delete mode 100644 infra/message-box-server/.github/workflows/deploy.yaml.example delete mode 100644 infra/message-box-server/.github/workflows/image.yaml delete mode 100644 infra/overlay-server/.github/workflows/image.yaml delete mode 100644 infra/uhrp-server-cloud-bucket/.github/dependabot.yml delete mode 100644 infra/uhrp-server-cloud-bucket/.github/workflows/build.yaml delete mode 100644 infra/uhrp-server-cloud-bucket/.github/workflows/deploy.yaml delete mode 100644 infra/uhrp-server-cloud-bucket/.github/workflows/setup.yaml delete mode 100644 infra/wab/.github/workflows/deploy.yaml.example delete mode 100644 infra/wallet-infra/.github/workflows/build-and-push.yml delete mode 100644 infra/wallet-infra/.github/workflows/build.yaml delete mode 100644 packages/helpers/amountinator/.github/workflows/push.yaml delete mode 100644 packages/helpers/bsv-wallet-helper/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 packages/helpers/bsv-wallet-helper/.github/ISSUE_TEMPLATE/discussion.md delete mode 100644 packages/helpers/bsv-wallet-helper/.github/pull_request_template.md delete mode 100644 packages/helpers/bsv-wallet-helper/.github/workflows/push.yaml create mode 100644 packages/helpers/create-bsv-app/AGENTS.md delete mode 100644 packages/helpers/did-client/.github/workflows/push.yaml create mode 100644 packages/helpers/did/AGENTS.md delete mode 100644 packages/helpers/simple/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 packages/helpers/simple/.github/ISSUE_TEMPLATE/discussion.md delete mode 100644 packages/helpers/simple/.github/pull_request_template.md delete mode 100644 packages/helpers/simple/.github/workflows/push.yaml delete mode 100644 packages/helpers/ts-templates/.github/workflows/push.yaml delete mode 100644 packages/helpers/ts-templates/CONTRIBUTING.md delete mode 100644 packages/messaging/authsocket-client/.github/workflows/push.yaml delete mode 100644 packages/messaging/authsocket/.github/workflows/push.yaml delete mode 100644 packages/messaging/ts-paymail/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 packages/messaging/ts-paymail/.github/ISSUE_TEMPLATE/discussion.md delete mode 100644 packages/messaging/ts-paymail/.github/pull_request_template.md delete mode 100644 packages/messaging/ts-paymail/.github/workflows/push.yaml delete mode 100644 packages/messaging/ts-paymail/CODE_OF_CONDUCT.md delete mode 100644 packages/messaging/ts-paymail/CONTRIBUTING.md create mode 100644 packages/messaging/ts-paymail/docs/examples/AGENTS.md delete mode 100644 packages/middleware/402-pay/.github/workflows/push.yaml delete mode 100644 packages/middleware/auth-express-middleware/.github/workflows/push.yaml create mode 100644 packages/middleware/auth/AGENTS.md delete mode 100644 packages/middleware/payment-express-middleware/.github/workflows/push.yaml delete mode 100644 packages/network/ts-p2p/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 packages/network/ts-p2p/.github/ISSUE_TEMPLATE/discussion.md delete mode 100644 packages/network/ts-p2p/.github/pull_request_template.md delete mode 100644 packages/network/ts-p2p/.github/workflows/push.yaml delete mode 100644 packages/network/ts-p2p/CODE_OF_CONDUCT.md delete mode 100644 packages/network/ts-p2p/CONTRIBUTING.md delete mode 100644 packages/overlays/gasp-core/.github/workflows/push.yaml delete mode 100644 packages/overlays/overlay-discovery-services/.github/workflows/push.yaml delete mode 100644 packages/overlays/overlay-express/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 packages/overlays/overlay-express/.github/ISSUE_TEMPLATE/discussion.md delete mode 100644 packages/overlays/overlay-express/.github/pull_request_template.md delete mode 100644 packages/overlays/overlay-express/.github/workflows/push.yaml delete mode 100644 packages/overlays/overlay/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 packages/overlays/overlay/.github/ISSUE_TEMPLATE/discussion.md delete mode 100644 packages/overlays/overlay/.github/pull_request_template.md delete mode 100644 packages/overlays/overlay/.github/workflows/push.yaml delete mode 100644 packages/overlays/overlay/CONTRIBUTING.md delete mode 100644 packages/sdk/CONTRIBUTING.md create mode 100644 packages/verifast/AGENTS.md create mode 100644 packages/wallet/wallet-toolbox-examples/AGENTS.md delete mode 100644 packages/wallet/wallet-toolbox/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 packages/wallet/wallet-toolbox/.github/ISSUE_TEMPLATE/discussion.md delete mode 100644 packages/wallet/wallet-toolbox/.github/pull_request_template.md delete mode 100644 packages/wallet/wallet-toolbox/.github/workflows/push.yaml delete mode 100644 packages/wallet/wallet-toolbox/CONTRIBUTING.md create mode 100644 packages/wallet/wallet-toolbox/client/AGENTS.md create mode 100644 packages/wallet/wallet-toolbox/mobile/AGENTS.md create mode 100644 scripts/contributor-policy.mjs create mode 100644 scripts/contributor-policy.test.mjs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ca4172a71..4a2034bde 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -17,10 +17,17 @@ conformance/ @sirdeggen @ty-everett @BraydenL # CI, release, dependency, and analysis controls require redundant ownership. .github/ @sirdeggen @ty-everett +/AGENTS.md @sirdeggen @ty-everett +/CLAUDE.md @sirdeggen @ty-everett +/CODE_OF_CONDUCT.md @sirdeggen @ty-everett +/CONTRIBUTING.md @sirdeggen @ty-everett /package.json @sirdeggen @ty-everett /pnpm-lock.yaml @sirdeggen @ty-everett /pnpm-workspace.yaml @sirdeggen @ty-everett +/governance/contributor-policy.json @sirdeggen @ty-everett /governance/repository-health/ @sirdeggen @ty-everett +/scripts/contributor-policy.mjs @sirdeggen @ty-everett +/scripts/contributor-policy.test.mjs @sirdeggen @ty-everett /scripts/repository-health.mjs @sirdeggen @ty-everett /scripts/repository-health.test.mjs @sirdeggen @ty-everett /sonar-project.properties @sirdeggen @ty-everett diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..496a0d52c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,81 @@ +name: Bug report +description: Report a reproducible defect in a ts-stack package, service, specification, or tool. +title: 'fix: ' +body: + - type: markdown + attributes: + value: | + Thanks for reporting a defect. Do not disclose vulnerabilities or secrets here; use the private security policy instead. + - type: dropdown + id: area + attributes: + label: Area + description: Select the closest affected area. + options: + - SDK or VeriFast + - Wallet + - Overlays + - Messaging + - Middleware + - Network + - Helpers + - Infrastructure or containers + - Conformance or specifications + - Documentation + - CI, dependencies, or release tooling + - Cross-stack or unsure + validations: + required: true + - type: input + id: packages + attributes: + label: Affected package or service and version + description: Include every known package/service and the installed, source, image, or deployment version. + placeholder: '@bsv/sdk 2.x; Node 24.x; browser/runtime details' + validations: + required: true + - type: textarea + id: summary + attributes: + label: Defect + description: Describe the observable problem, its impact, and whether it is a regression. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Minimal reproduction + description: Provide deterministic steps or a small repository/code sample. Remove credentials and private data. + render: shell + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + description: Cite a public API, specification, conformance vector, or prior compatible behavior when applicable. + validations: + required: true + - type: textarea + id: actual + attributes: + label: Actual behavior and evidence + description: Include complete errors, logs, or screenshots after redacting secrets. + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: OS, Node, package manager, browser/mobile runtime, database, image digest, and relevant configuration. + validations: + required: true + - type: checkboxes + id: checks + attributes: + label: Safety checks + options: + - label: I searched for an existing issue or pull request describing this defect. + required: true + - label: This report contains no secret, private key, token, production data, or uncoordinated vulnerability detail. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..5130e5e48 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Private security report + url: https://github.com/bsv-blockchain/ts-stack/security/advisories/new + about: Report vulnerabilities privately. Do not open a public security issue. diff --git a/.github/ISSUE_TEMPLATE/proposal.yml b/.github/ISSUE_TEMPLATE/proposal.yml new file mode 100644 index 000000000..869858963 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/proposal.yml @@ -0,0 +1,59 @@ +name: Change proposal +description: Propose a capability, specification, public API, package, migration, or cross-stack improvement. +title: 'proposal: ' +body: + - type: markdown + attributes: + value: | + Substantial changes should establish compatibility, specification, security, and migration intent before implementation. + - type: textarea + id: problem + attributes: + label: Problem and users + description: What concrete problem exists, who encounters it, and why current behavior is insufficient? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed end state + description: Describe behavior and acceptance criteria, not only an implementation. + validations: + required: true + - type: textarea + id: scope + attributes: + label: Affected packages, services, and implementations + description: Include TypeScript projects, public services, specifications, conformance vectors, and other implementations. + validations: + required: true + - type: textarea + id: compatibility + attributes: + label: Compatibility and migration + description: Explain public API, wire, persistence, runtime, browser/mobile, deployment, and cross-implementation effects. + validations: + required: true + - type: textarea + id: security + attributes: + label: Security and operational impact + description: Identify changed trust boundaries, abuse cases, CORS/CSP behavior, dependencies, rollout, and rollback. + validations: + required: true + - type: textarea + id: evidence + attributes: + label: Validation plan + description: List tests, conformance, consumer, performance, documentation, and release evidence required. + validations: + required: true + - type: checkboxes + id: commitments + attributes: + label: Proposal checks + options: + - label: I searched for overlapping issues, pull requests, specifications, and planning records. + required: true + - label: I have not assumed that a breaking change is acceptable merely because it simplifies implementation. + required: true diff --git a/.github/SECURITY.md b/.github/SECURITY.md index f663cacbc..053111998 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -92,7 +92,7 @@ Pull requests and releases enforce, as applicable: Coverage-guided fuzzing, remaining manual/browser/performance review, and the final QA hardening campaign are deliberately tracked as unfinished in -[issue #324](https://github.com/bsv-blockchain/ts-stack/issues/324). They must +[issue #400](https://github.com/bsv-blockchain/ts-stack/issues/400). They must not be described as complete until their preserved branch is finished, reviewed, merged, and validated. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 58f5519f9..323e42e00 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -108,7 +108,7 @@ updates: - dependency-name: node # Database majors require backup/restore, upgrade-path, rollback, and # application compatibility validation. Patch and minor refreshes remain - # automated; major migrations are tracked explicitly in issue #324. + # automated; each major migration requires its own reviewed issue. - dependency-name: mysql update-types: - version-update:semver-major diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 77eb1c1a2..59da0f7ff 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,9 +1,14 @@ +> Keep this pull request in draft until local validation is complete. After +> every push, wait for all applicable checks on the exact head to finish and +> fix every failure before requesting review or calling the work complete. + ## Program and scope - Tracker or issue: - Program gate(s) advanced: - Why this change is needed: - Explicitly out of scope: +- Exact head SHA reviewed: ## Impact @@ -26,6 +31,10 @@ through the release workflow after approval): - Lint/typecheck delta: - Browser/mobile/packed-consumer evidence: - Performance or bundle-size delta: +- [ ] I self-reviewed the complete diff for correctness, security, + compatibility, public API, artifacts, dependencies, docs, and operations +- [ ] All applicable checks are terminal and successful on the exact head; any + scope-based skip is expected and validated by the merge gate ## Security and dependencies @@ -33,6 +42,7 @@ through the release workflow after approval): - [ ] Changelog, runtime relevance, peer compatibility, transitive graph, and audit results were reviewed - [ ] CodeQL/negative tests cover any changed trust boundary +- [ ] The exact-head CodeQL analysis has no new alert - [ ] The exact-head repository quality gate reports zero new Sonar findings (including accepted or false-positive issue states) and zero unreviewed hotspots; Sonar's aggregate `Quality Gate passed` verdict alone is not merge evidence @@ -68,5 +78,9 @@ changes. Use “Not applicable — ” only when the reason is concrete. - [ ] The linked tracker is updated only for work fully proved by merged code, passing checks, resolved alerts, measurements, or an approved exception - [ ] Review conversations are resolved +- [ ] Documentation, changelog, migration notes, release notes, and operator + guidance are current or concretely not applicable +- [ ] No pending, failed, stale, cancelled, or unexpectedly skipped check is + being handed to another contributor as “complete” - [ ] One qualified maintainer approval is sufficient; no last-pusher restriction is assumed diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc6e19a8d..4f862c458 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,9 @@ jobs: - name: Test repository health controls run: node --test scripts/*.test.mjs + - name: Enforce uniform contributor and agent policy + run: node scripts/contributor-policy.mjs + - name: Enforce the TypeScript compiler and tooling-API boundary run: node scripts/typescript-toolchain.mjs diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..b405f3867 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,139 @@ +# ts-stack agent instructions + +These instructions apply to every file in this repository. They are the only +repository contribution instructions for AI agents. A package-level +`AGENTS.md` is a pointer to this file, not a place to define different rules. + +## Read before changing anything + +1. Read [`CONTRIBUTING.md`](./CONTRIBUTING.md), the relevant package or service + README, and any applicable material under `docs/`, `specs/`, and + `governance/`. +2. Read [`.github/SECURITY.md`](./.github/SECURITY.md) before touching a trust + boundary, dependency, workflow, release, credential, public service, or + persistence behavior. +3. Establish the current `main` state, inspect existing changes, and identify + the governed project profile and criticality in + `governance/repository-health/projects.json`. Infrastructure service + contracts live in `governance/service-operations.json`. +4. Keep work narrowly scoped. Do not overwrite unrelated changes or + regenerate unrelated artifacts. + +Do not create package-local contribution rules, agent instructions, pull +request templates, issue templates, Dependabot files, or workflows. Propose +shared policy at the repository root. Package-specific technical information +belongs in its README, `docs/`, `specs/`, or an operator guide. + +## Preserve contracts first + +- Specifications, conformance vectors, public declarations, documented + behavior, and established cross-implementation behavior are contracts. +- Prefer additive and backward-compatible changes. Do not rename or remove + exports, narrow accepted input, change defaults, alter wire encodings, + serialization, persisted schemas, error identities, runtime targets, or + browser/mobile behavior without an explicitly approved migration. +- A generic cleanup, analyzer suggestion, dependency upgrade, or refactor is + never sufficient reason for a breaking change. +- When behavior is portable across BSV implementations, update or add shared + conformance evidence and consider compatibility with implementations outside + this repository. +- Tier 0 projects (`@bsv/sdk`, `@bsv/verifast`, and + `@bsv/wallet-toolbox`) require the highest review bar. Treat cryptography, + Script/consensus logic, transaction encoding, WASM/worker boundaries, wallet + storage, signing, and remotely exposed trust boundaries as security- and + compatibility-critical. + +For public services, preserve credential-free public cross-domain access by +default where it is already part of the service contract. Overlay, Wallet +Storage, WAB, Message Box, relay, browser, mobile, and unknown-domain clients +must not be silently blocked by CORS, CSP, hosting URLs, or origin checks. +Allowlist modes are opt-in deployment policy; authentication, authorization, +signatures, validation, rate limits, and request bounds provide security. + +## Implementation discipline + +- Understand the root cause and deployed impact before editing. +- Prefer the smallest clear solution that removes the cause without hiding a + finding or weakening a check. +- Keep authored code warning-free, strictly typed, formatted, and + understandable. Do not use generated output, suppression, exclusions, + accepted findings, false-positive status, skipped tests, or baselines to + conceal new debt. +- Add tests that fail on the old behavior when practical. Cover negative, + boundary, interoperability, and compatibility cases appropriate to the + change. +- Review the complete diff as a maintainer would: correctness, security, + compatibility, public API, package artifacts, performance, documentation, + migration, release, and operational impact. +- Update documentation in the same change. Documentation, examples, manifests, + generated facts, release notes, and code must never intentionally drift. + +## Dependencies and generated files + +- Treat Dependabot and other automation as proposals, not approvals. Review + upstream release notes, necessity, runtime and peer compatibility, + transitive changes, lockfile deduplication, advisories, CodeQL impact, + package consumers, and bundle/performance effects. +- First-party `@bsv/*` versions are coordinated by the repository release + process, not generic dependency automation. +- Never hand-edit owned generated files. Change their source or generator and + run the documented deterministic generation check. +- Do not add an override, quality exception, advisory dismissal, or dependency + hold unless no safe remediation exists and the governed registry records an + owner, evidence, review date, removal condition, and compatibility rationale. + +## Validation + +Use Node and pnpm versions from the root `package.json`. Run the strictest +relevant local checks before spending hosted CI resources. At minimum, every +change must pass: + +```sh +pnpm health:check +pnpm lint +pnpm format:check +pnpm typecheck +``` + +Run build and tests for every affected package and dependent behavior. Add the +applicable conformance, coverage, packed-consumer, browser, mobile, property, +mutation, documentation, security-audit, infrastructure, container, or +performance checks described in [`CONTRIBUTING.md`](./CONTRIBUTING.md). A local +shortcut may speed iteration but cannot replace the remote merge gate. + +## Pull requests and completion + +- Fill in the root pull request template with commands and concrete evidence; + do not check a box that has not been proved. +- Open unfinished work as a draft. After each push, monitor the exact head + until every applicable check reaches a terminal successful state. An + expected scope-based skip is acceptable only when the repository merge gate + validates it; a missing, cancelled, stale, or unexpectedly skipped check is + not success. +- A PR is not ready for handoff, review, merge, or a claim of completion while + CI is pending or failing, review threads are open, or Sonar/CodeQL has a new + finding. Continue working through failures; do not leave them for another + contributor without an explicit handoff request. +- “Quality gate passed” means the complete repository gate passed for the + exact head. SonarCloud’s aggregate badge alone is not evidence. New Sonar + issues—including accepted or false-positive classifications—new unreviewed + hotspots, and new CodeQL alerts must be resolved before review. +- One qualified maintainer approval is sufficient. Maintainers and + administrators may facilitate a merge after required checks and review + threads are complete; no independent last-pusher rule is assumed. +- Re-read the final diff and verify the head SHA before review or merge. After + merge, verify `main` when the change affects shared controls, releases, or + deployed behavior. + +## Versions, notes, and releases + +Follow `docs/about/versioning.md` and the protected release workflows. +Published-byte or manifest changes require the correct affected-package SemVer +decision, an updated `governance/package-release-notes.json` entry, current +package documentation, and migration guidance—even when the migration is +“none.” Update a package-local changelog when that package already maintains +one. + +Never publish npm packages or container images, create release tags, or deploy +from a workstation unless an operator explicitly authorizes that separate +action. Merging source is not publication, and publication is not deployment. diff --git a/CLAUDE.md b/CLAUDE.md index 6e3b5739f..b60f42eaa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,41 +1,10 @@ -# ts-stack — repo guidance +# Claude instructions -## Releasing infra images (general + Marketplace) +Read and follow [`AGENTS.md`](./AGENTS.md) before changing this repository. +The canonical contribution policy is [`CONTRIBUTING.md`](./CONTRIBUTING.md). -Infra components under `infra/*` publish **two independent channels** when you push an -`infra/v*` tag (or run the workflows manually). Marketplace does **not** replace the general -image path. +Do not create Claude-specific package conventions. Package technical details +belong in current READMEs, `docs/`, `specs/`, and `governance/`. -### 1. General OCI image → GHCR (primary for self-hosters) - -- Workflow: `.github/workflows/infra-release.yaml` -- For every `infra/` with a Dockerfile + `package.json` version not yet in GHCR, builds and - pushes `ghcr.io//:v` (e.g. `ghcr.io/bsv-blockchain/wab:v1.4.7`). -- Version source of truth: each component’s `package.json` `version`. The git tag is only the - trigger (date-based); it is never parsed for a version. -- Use this image in your own Kubernetes, Compose, or other systems. - -### 2. AWS Marketplace listing → Marketplace ECR + Catalog (WAB only) - -- Workflow: `.github/workflows/wab-marketplace-release.yml` (must be at repo root — GitHub does - not run workflows under `infra/wab/.github/workflows/`). -- Same trigger and same `infra/wab/package.json` version source of truth as GHCR. -- Marketplace version titles are `v`-prefixed (`v1.4.7`) to match the existing listing; the - Marketplace ECR image tag is bare (`wab:1.4.7`). -- Product ID: `prod-67ziqtkrihz34` (ContainerProduct@1.0). -- Image for the listing goes to Marketplace ECR - `709825985650.dkr.ecr.us-east-1.amazonaws.com/bsv-blockchain/wab:` **in addition to** - GHCR — not instead of it. Docker Hub (`bsvb/wab`) is legacy and no longer published. -- Publishing = push image to Marketplace ECR **then** register via Catalog API - `StartChangeSet` / `AddDeliveryOptions`. Pushing the image alone does nothing buyers can see. -- Version names on the listing are immutable and must be unique; never reuse one. -- New versions are scanned (minutes–hours) before going live. -- Required repo variable: `WAB_MP_ROLE_ARN` (OIDC role in the seller account with Marketplace ECR - push + Catalog permissions). See workflow header comments for details. - -### How to release - -1. Bump `infra//package.json` `version` as needed. -2. Push an `infra/v*` tag (or `workflow_dispatch` the relevant workflow(s)). -3. Confirm GHCR tags on GitHub Packages; for WAB Marketplace, confirm the listing version after - the change set reaches `SUCCEEDED`. +Release and operations guidance lives in +[`docs/reference/release-operations.md`](./docs/reference/release-operations.md). diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..879b7eca9 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,40 @@ +# Code of Conduct + +The ts-stack community is committed to respectful, constructive collaboration +in service of excellent, secure software. + +## Expected behavior + +Contributors and maintainers must: + +- treat other people with respect and dignity; +- welcome relevant technical questions and differing viewpoints; +- critique ideas and code without demeaning the person; +- communicate clearly, honestly, and professionally; +- respect privacy and coordinated vulnerability disclosure; and +- accept responsibility, correct mistakes, and help restore productive + collaboration. + +Harassment, threats, discrimination, deliberate intimidation, sexualized +attention, doxxing, publication of private information, and sustained +disruption are not acceptable in project spaces. + +## Scope and enforcement + +This policy applies in repository issues, pull requests, reviews, discussions, +project events, and public interactions where someone represents the project. +Maintainers may edit or remove harmful content, warn a participant, limit +participation, or temporarily or permanently ban someone when necessary. +Enforcement decisions should be timely, proportionate, documented privately, +and free from conflicts of interest. + +Report GitHub-hosted abuse through GitHub’s reporting tools and contact a +project or organization maintainer privately when project action is needed. Do +not use a public issue for a report that would expose a target or sensitive +details. Security vulnerabilities follow [the separate private disclosure +policy](./.github/SECURITY.md). + +Good-faith technical disagreement, maintainer review, and evidence-based +rejection of a change are not misconduct. Decisions about code must be based on +correctness, security, compatibility, maintainability, and the project’s +documented standards. diff --git a/CONFORMANCE_AND_DOCS_AUDIT.md b/CONFORMANCE_AND_DOCS_AUDIT.md index e71c013f6..5492d5363 100644 --- a/CONFORMANCE_AND_DOCS_AUDIT.md +++ b/CONFORMANCE_AND_DOCS_AUDIT.md @@ -3,8 +3,10 @@ > **Archived point-in-time evidence (2026-05-14).** Findings and counts in this > report are not the current backlog. Use > `docs/reference/stack-facts.md`, `conformance/PARITY_MATRIX.json`, and -> [tracker #324](https://github.com/bsv-blockchain/ts-stack/issues/324) for the -> enforced current state. This report remains committed only for provenance. +> `docs/reference/repository-health.md` for the enforced current state. Final +> conformance/QA work is tracked in +> [issue #400](https://github.com/bsv-blockchain/ts-stack/issues/400). This +> report remains committed only for provenance. **Date**: 2026-05-14 **Purpose**: Pre-port audit for Golang, Python, and Rust SDK/wallet implementations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..5226238d2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,258 @@ +# Contributing to ts-stack + +Thank you for improving the BSV TypeScript stack. This monorepo is one +coordinated system: shared specifications, public packages, infrastructure, +documentation, conformance vectors, quality controls, and release machinery +must evolve without drifting apart. + +This is the canonical contribution policy for the entire repository. Package +directories do not define separate conventions. AI agents must also read the +root [`AGENTS.md`](./AGENTS.md). + +## Community and security + +Follow the [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md). Report vulnerabilities +privately under [the security policy](./.github/SECURITY.md); never put secrets, +exploit details, private keys, tokens, production data, or uncoordinated +vulnerability reports in a public issue or pull request. + +Use the root issue forms for reproducible defects and proposals. Discuss a +substantial new capability, new package, specification change, public API +change, or migration before implementing it. A narrowly scoped compatible fix +may proceed directly when its intent and evidence are clear. + +## Repository authority + +The current sources of truth are: + +- `package.json`, `pnpm-workspace.yaml`, and package manifests for toolchain, + workspace, and package metadata; +- `governance/repository-health/projects.json` for project ownership, + profiles, criticality, runtime targets, and release routes; +- `specs/` and `conformance/` for portable protocol contracts; +- `docs/` and package READMEs for supported usage and operations; +- `governance/` for machine-enforced policy, exceptions, package release notes, + service contracts, and artifact ownership; and +- root `.github/` for all issue, pull request, dependency, analysis, CI, and + release automation. + +Historical planning records and imported package files are evidence, not +authority, when they disagree with current code, specifications, governance, +or CI. + +## Set up a safe workspace + +Use a current branch from `main` and the exact toolchain declared at the root: + +```sh +corepack enable +pnpm install --frozen-lockfile --ignore-scripts +pnpm rebuild esbuild +``` + +The install denies dependency lifecycle scripts, matching CI’s supply-chain +boundary. Rebuild only a repository-audited native tool when an applicable +workflow or package guide requires it. + +Before editing: + +1. inspect the working tree and preserve unrelated changes; +2. identify all affected packages, dependents, runtimes, specifications, + generated artifacts, and deployed services; +3. read their README and relevant docs/specs; +4. inspect open issues and pull requests for overlapping work; and +5. determine the project tier and required consumer profiles from governance. + +## Design and compatibility rules + +The default expectation is no consumer breakage. + +- Preserve public exports, declaration shapes, import conditions, supported + runtime targets, documented inputs, defaults, errors, wire formats, + serialization, persistence, browser/mobile behavior, and operational + contracts. +- Prefer additive changes and deprecation over removal. A breaking change + requires explicit maintainer approval, the appropriate SemVer release, + before/after migration instructions, cross-package coordination, and a + rollback or forward-fix plan. +- Treat specifications and conformance vectors as implementation-independent + contracts. A TypeScript-only convenience must not accidentally make the + reference behavior incompatible with another conforming implementation. +- Do not make a behavior change solely to appease an analyzer. Determine + whether the finding is real, fix it safely, and retain a governed, + evidence-backed exception only when compatibility or correctness genuinely + requires it. + +Tier 0 changes in `@bsv/sdk`, `@bsv/verifast`, and +`@bsv/wallet-toolbox` receive the most rigorous self-review and evidence. +Cryptography, Script/consensus behavior, transaction encodings, WASM/worker +interfaces, wallet persistence/signing, authentication, remote input, and +release controls require explicit trust-boundary and compatibility review. + +Public services intentionally support deployed applications, browsers, WUI, +mobile devices, and unknown future domains. Preserve public, credential-free +wildcard CORS where it is the established default. Operators may opt into +allowlists, but CORS/CSP/origin filtering must not replace protocol +authentication, signatures, authorization, validation, rate limits, or bounds. + +## Code, tests, and documentation move together + +Every change should leave the repository easier to verify: + +- keep TypeScript strict and lint output at zero warnings; +- use the root Oxlint and Prettier policies without package-local alternatives; +- add focused regression and negative tests for changed behavior; +- add integration, consumer, conformance, property, mutation, browser, mobile, + or operational evidence when the boundary warrants it; +- update public JSDoc, READMEs, examples, generated documentation, support + claims, specifications, and runbooks in the same pull request; and +- regenerate owned artifacts from their source—never edit generated bytes to + disguise drift. + +Tests must contain meaningful assertions. Do not skip, weaken, delete, rename, +or reclassify a test merely to make CI green. Manual, live, resource-intensive, +property, mutation, and conformance gaps follow the governed test-quality +registries and removal conditions. + +## Local validation + +Run checks locally before opening or updating a pull request whenever feasible. +Start with the invariant repository controls: + +```sh +pnpm health:check +pnpm lint +pnpm format:check +pnpm typecheck +pnpm audit:security +``` + +Then run the checks implied by the change: + +```sh +# Workspace build and deterministic tests +pnpm build +pnpm test + +# Affected package examples +pnpm --filter @bsv/sdk test +pnpm --filter @bsv/sdk test:coverage +pnpm --filter @bsv/sdk pack:check + +# Portable behavior and documentation +pnpm conformance +pnpm docs:facts:check +pnpm docs:examples +pnpm docs:build + +# Governed high-risk boundaries +pnpm test:property +pnpm test:mutation --target +``` + +Use the package’s declared `test:browser`, `test:mobile`, `test:consumers`, or +other profile command when applicable. Root toolchain, SDK, CI, or governance +changes may select the full workspace and mutation registry. Infrastructure or +container changes require their root CI matrix and runtime-contract evidence; +do not substitute a macOS image build for hosted Linux/amd64 validation. + +Local checks are necessary evidence, not authority to bypass remote checks. +Record the exact commands and results in the pull request. + +## Dependencies and automation + +Dependabot identifies candidates; it does not decide that an update is safe. +For every manifest, lockfile, container base, action, or generator dependency +change, review and record: + +1. upstream release notes, security relevance, and why the change is needed; +2. Node/browser/mobile/build/runtime and peer compatibility; +3. the complete transitive graph and deduplicated frozen lock; +4. high/critical audit results and CodeQL/security impact; +5. affected package, packed-consumer, conformance, and platform tests; +6. bundle-size and performance effects; and +7. affected public package versions and release notes. + +First-party `@bsv/*` dependencies are synchronized through the protected +release-aware graph. Do not re-enable generic automation for unpublished +sibling versions. Major runtime, database, compiler, React Native, build, and +container migrations are coordinated programs, not routine bumps. + +Do not introduce a broad override or suppression. If no compatible upstream +fix exists, register the narrowest temporary exception with an owner, evidence, +review date, objective removal condition, and tests. Rehearse removal after +relevant upstream releases. + +## Versions, changelogs, and migrations + +Follow [`docs/about/versioning.md`](./docs/about/versioning.md). + +When a public package’s published bytes or manifest change: + +1. choose the SemVer impact from the consumer-visible contract; +2. bump only affected packages and dependents whose packed contract changes; +3. update the exact entry in `governance/package-release-notes.json`; +4. regenerate the package API and migration ledger with + `pnpm docs:packages`; +5. update the package README, API docs, and examples as needed; and +6. update a package-local `CHANGELOG.md` when that package already has one. + +Every release-note entry includes a migration decision. Say explicitly that no +consumer migration is required when behavior is unchanged. An incompatible +change needs before/after examples, deployment and persistence impact, +coordination with first-party dependents and other implementations, and a +rollback or forward-fix plan. + +Repository-only policy or documentation that is excluded from a package +artifact does not require a cosmetic version bump. + +## Pull request lifecycle + +Use the root pull request template and keep the PR draft while work or evidence +is incomplete. + +Before pushing, self-review the complete diff for: + +- correctness and root cause; +- security and trust-boundary impact; +- public API, wire, persistence, runtime, and cross-implementation + compatibility; +- tests, coverage, package artifacts, bundle size, and performance; +- dependency and release implications; +- documentation, changelog, migration, and operator impact; and +- accidental generated, vendored, secret, or unrelated changes. + +After every push, remain responsible for the exact head: + +1. wait for all applicable GitHub checks to reach a terminal state; +2. fix every failure or unexpected skip and rerun it; +3. resolve all review conversations; +4. require zero new Sonar findings, zero unreviewed Sonar hotspots, and zero new + CodeQL alerts; and +5. verify that the reviewed SHA is still the PR head. + +SonarCloud’s aggregate “Quality Gate passed” result is not sufficient. The +repository-owned exact-head zero-finding job and complete merge gate are the +authority. Reclassifying a new issue as accepted or false-positive does not +make it mergeable. + +Do not call a PR complete or hand it off with pending or failed CI. If a check +is transient, rerun and continue monitoring. If work must genuinely transfer +to another person, describe the unresolved state explicitly instead of +presenting it as finished. + +One qualified maintainer approval is sufficient. Maintainers and admins may +merge after the exact head is green and review threads are resolved; an +independent last-pusher is not required. + +## Releases and deployments + +Source merge, package publication, image publication, and deployment are +separate states. Do not publish from a workstation or create a release tag +unless an operator explicitly requests that action. Protected root workflows +build immutable candidates, scan them, generate SBOMs, attest provenance, and +reconcile registry state. + +For a release or deployed change, retain source SHA, workflow, artifact digest, +version, migration/rollback evidence, and live validation as applicable. Never +claim availability from a local build. diff --git a/README.md b/README.md index 45130ab20..b1d64b95f 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,19 @@ BSV TypeScript monorepo for the SDK, wallet tooling, overlays, messaging, middle Most applications should start with `@bsv/simple` or `@bsv/sdk`. Wallet builders usually start with `@bsv/wallet-toolbox`. Service operators usually start with the overlay, messaging, middleware, or infra packages. +## Contributing + +The entire stack follows one [contribution policy](./CONTRIBUTING.md), one +[agent doctrine](./AGENTS.md), one [code of conduct](./CODE_OF_CONDUCT.md), and +the root [security policy](./.github/SECURITY.md). Package-level `AGENTS.md` +files point back to these root rules; packages do not define competing +conventions. + +Use the root issue and pull request templates. Run strict local checks before +using hosted CI, then keep a pull request in draft and remain responsible for +its exact head until all applicable checks pass with zero new Sonar or CodeQL +findings. + ## Quick Start Prerequisites: @@ -45,7 +58,7 @@ Useful root commands: | ---------------------- | ------------------------------------------------------------------------------------- | | `pnpm build` | Builds every workspace package except the private root package | | `pnpm test` | Runs every workspace package test script | -| `pnpm lint` | Lints every authored JS/TS file with a repository-wide zero-warning gate | +| `pnpm lint` | Lints every authored JS/TS file with a repository-wide zero-warning gate | | `pnpm format:check` | Verifies repository and workspace formatting without changing files | | `pnpm health:check` | Enforces repository profiles, package metadata, licensing, and policy synchronization | | `pnpm test:governance` | Verifies required skips, manual/live suites, empty-test policy, and conformance gaps | diff --git a/conformance/runner/AGENTS.md b/conformance/runner/AGENTS.md new file mode 100644 index 000000000..da1c81f82 --- /dev/null +++ b/conformance/runner/AGENTS.md @@ -0,0 +1,10 @@ +# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](../../AGENTS.md) +and [contribution policy](../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/conformance/runner/ts/AGENTS.md b/conformance/runner/ts/AGENTS.md new file mode 100644 index 000000000..dcea67c80 --- /dev/null +++ b/conformance/runner/ts/AGENTS.md @@ -0,0 +1,10 @@ +# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/docs-site/AGENTS.md b/docs-site/AGENTS.md new file mode 100644 index 000000000..e3cea74cd --- /dev/null +++ b/docs-site/AGENTS.md @@ -0,0 +1,10 @@ +# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](../AGENTS.md) +and [contribution policy](../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/docs/about/contributing.md b/docs/about/contributing.md index ff839020d..bb29d394a 100644 --- a/docs/about/contributing.md +++ b/docs/about/contributing.md @@ -2,9 +2,9 @@ id: about-contributing title: 'Contributing' kind: meta -version: '1.0.1' -last_updated: '2026-07-29' -last_verified: '2026-07-29' +version: '2.0.0' +last_updated: '2026-07-30' +last_verified: '2026-07-30' review_cadence_days: 30 status: stable tags: [about, contributing, development, community] @@ -12,430 +12,41 @@ tags: [about, contributing, development, community] # Contributing to ts-stack -We welcome contributions from the community! This guide explains how to contribute. - -## Ways to Contribute - -- **Report bugs** — Open an issue with a minimal reproduction -- **Suggest features** — Discuss before starting major work -- **Submit PRs** — Fork, branch, commit, push, create PR -- **Improve docs** — Help clarify or expand documentation -- **Add conformance vectors** — Test cases for protocols -- **Fix failing tests** — Contribute bug fix vectors - -## Getting Started - -### 1. Clone the Repository - -```bash -git clone https://github.com/bsv-blockchain/ts-stack -cd ts-stack -``` - -### 2. Install Dependencies - -```bash -pnpm install -``` - -The project uses **pnpm workspaces** for multi-package management. - -### 3. Explore the Structure - -``` -ts-stack/ - packages/ # 30 public npm packages plus private workspaces - conformance/ # Test vectors and runners - docs/ # Documentation source (Markdown) - docs-site/ # Vite+React+MDX docs site - .github/workflows/ # CI/CD pipelines -``` - -## Development Workflow - -### Create a Feature Branch - -```bash -git checkout -b feature/your-feature-name -``` - -Branch naming conventions: - -- `feature/...` — New features -- `fix/...` — Bug fixes -- `docs/...` — Documentation -- `chore/...` — Maintenance -- `test/...` — Test additions - -### Make Changes - -Follow code style guidelines: - -- **TypeScript** with strict mode enabled -- **2 spaces** for indentation -- **Meaningful names** for variables/functions -- **JSDoc comments** for public APIs -- **No console.log** in production code - -Example: - -```typescript -/** - * Sign a message using the private key. - * - * @param message - The message to sign - * @returns The signature hex string - * @throws {Error} If signing fails - */ -export function sign(message: string): string { - // Implementation -} -``` - -### Run Tests - -```bash -# Build declarations, then run all package tests -pnpm build -pnpm test - -# Specific package -pnpm --filter @bsv/sdk test - -# Package coverage -pnpm --filter @bsv/sdk test:coverage -``` - -Required tests must not be empty or anonymously skipped. Manual, live-network, -resource-intensive, and intended conformance gaps follow the -[test-quality governance contract](../reference/test-quality-governance/). -Run `pnpm test:governance` before submitting test-classification changes. New -untrusted parsers, codecs, serializers, signature framing, or network -destination logic should include a registered property suite; use -`pnpm test:property` to run the complete reproducible campaign. Every property -suite also owns a focused mutation target so its assertions are proven capable -of detecting semantic defects: - -```bash -# List the governed targets -pnpm test:mutation --list - -# Run the target for the boundary being changed -pnpm test:mutation --target p2p-messages -``` - -When a mutant survives, add the missing observable assertion or correlated -input case when it represents a real behavior gap. Do not add broad mutation -exclusions or weaken the ratchet to make a pull request pass. - -### Lint Code - -```bash -# Check linting -pnpm lint - -# Fix linting issues in a package -pnpm --filter @bsv/sdk exec oxlint --fix src -``` - -Uses **Oxlint** for fast, consistent TypeScript checks. The root command scans -all 1,909 authored JavaScript and TypeScript files on every run; any error or -warning fails CI. The root `.oxlintrc.json` is the shared baseline: -it enables the correctness profile, rejects non-`node:` built-in imports, and -defines Node, browser, and test environments for their repository paths. -Package scripts may provide a faster local package scope, but must not weaken -the shared rules, exclude authored tests or benchmarks, or omit the -zero-warning gate. Legacy ESLint files and ESLint-only TypeScript configs are -not part of the lint contract. - -### Check Formatting - -```bash -pnpm format:check -``` - -The formatting check is read-only. The root `.prettierrc.json` is the single -formatting policy; its path-specific overrides preserve the established -Wallet Toolbox and wallet-infrastructure print widths. Run the relevant -package's Prettier command with `--write` before submitting when it reports a -mismatch. - -### Build Packages - -```bash -# Build all packages -pnpm build - -# Build specific package -pnpm build --filter=@bsv/sdk -``` - -## Adding Conformance Vectors - -Add or update a conformance vector when a change fixes or modifies a portable -protocol behavior. See -[Contributing Vectors](../conformance/contributing-vectors.md) for details. - -Example: - -```bash -# Create vector file -cat > conformance/vectors/wallet/brc100/createAction-negative-satoshis.json < -- OS: macOS 15.6 - -### Reproduction - -```typescript -// Minimal code to reproduce -``` - -### Expected - -[Expected behavior] - -### Actual - -[Actual behavior] -```` - -## Documentation - -Help improve docs: - -1. Find unclear sections in `docs/` -2. Edit the relevant `.md` file -3. Preview locally: `pnpm docs:dev` → `http://localhost:5173` -4. Run `pnpm docs:facts:check` when package, runtime, release, README, or - conformance facts changed. -5. Submit a PR — the docs site build (`pnpm docs:build`) validates frontmatter - and links (run `pnpm --filter docs-site validate` locally first). - -**Do not delete the `gh-pages` branch** — it hosts the TypeDoc `/api/` tree that -the deploy workflow merges into the artifact before publishing. - -See [Versioning Policy](./versioning.md) for documentation maintenance. - -## Commit Message Format - -Use conventional commits: - -``` -type(scope): subject - -body -footer -``` - -Types: - -- `feat:` — New feature -- `fix:` — Bug fix -- `docs:` — Documentation -- `style:` — Code style (no behavior change) -- `refactor:` — Code refactoring -- `perf:` — Performance improvements -- `test:` — Test additions/changes -- `chore:` — Build, CI, dependencies - -Examples: - -``` -feat(brc100): add getBalance method -fix: prevent double-spend in UTXO selection -docs: clarify BRC-31 authentication flow -test: add vectors for negative satoshis -``` - -## Continuous Integration - -All PRs run through GitHub Actions: - -1. **Unit tests** — Must pass -2. **Linting** — Must pass -3. **Type checking** — Must pass -4. **Conformance** — structural and TypeScript behavior runners must pass -5. **Build** — All packages must build - -If any check fails, fix the issue and push again. - -## Licensing - -By contributing, you agree that your contributions are licensed under the -[Open BSV License Version 6](https://github.com/bsv-blockchain/ts-stack/blob/main/LICENSE.txt), -the same license used uniformly throughout ts-stack. See the -[licensing policy](../reference/licensing.md) for the package and release -controls that keep every first-party project on the current canonical text. - -## Code of Conduct - -Be respectful and constructive. We're here to help each other build better software. - -## Questions? - -- Open an issue with your question -- Join our community chat -- Reach out to maintainers - -## Next Steps - -- [Conformance Contributing Guide](../conformance/contributing-vectors.md) -- [Versioning Policy](./versioning.md) -- [Doc Agent Guide](./doc-agent.md) +The canonical, repository-wide contribution policy is the root +[`CONTRIBUTING.md`](https://github.com/bsv-blockchain/ts-stack/blob/main/CONTRIBUTING.md). +AI agents must also follow the root +[`AGENTS.md`](https://github.com/bsv-blockchain/ts-stack/blob/main/AGENTS.md). + +Those files apply to every package, service, specification, conformance +project, documentation surface, and workflow. Package-level `AGENTS.md` files +are generated pointers to the root; packages do not define separate lint, +review, dependency, release, documentation, migration, or quality rules. + +The canonical policy covers: + +- safe workspace setup and the root Node/pnpm toolchain; +- specifications, cross-implementation compatibility, and Tier 0 review; +- strict TypeScript, zero-warning lint, formatting, testing, conformance, + package consumers, browser/mobile/WASM, fuzz/property, mutation, security, + infrastructure, and documentation validation; +- public-service CORS/CSP compatibility; +- dependency and Dependabot review; +- standardized SemVer, package release notes, changelogs, and migration notes; +- exact-head self-review, Sonar/CodeQL zero-finding requirements, and the rule + that work is not handed off or called complete while CI is pending or + failing; and +- protected publication, provenance, deployment, and rollback boundaries. + +Use the root issue forms and pull request template. Security vulnerabilities +must be reported privately under the +[Security Policy](https://github.com/bsv-blockchain/ts-stack/security/policy). + +Current focused follow-up programs are: + +- [Deferred final QA: coverage, fuzzing, conformance, and runtime validation](https://github.com/bsv-blockchain/ts-stack/issues/400); +- [Authorized package/image release and external assurance](https://github.com/bsv-blockchain/ts-stack/issues/401); and +- [Sonar administration and CI efficiency review](https://github.com/bsv-blockchain/ts-stack/issues/402). + +The completed modernization history remains in retired +[tracker #324](https://github.com/bsv-blockchain/ts-stack/issues/324); it is no +longer the source for contribution policy or active work. diff --git a/docs/about/doc-agent.md b/docs/about/doc-agent.md index e6fcf6d8f..ab2312b72 100644 --- a/docs/about/doc-agent.md +++ b/docs/about/doc-agent.md @@ -2,9 +2,9 @@ id: about-doc-agent title: 'Documentation Maintenance' kind: meta -version: '2.0.0' -last_updated: '2026-07-27' -last_verified: '2026-07-27' +version: '2.1.0' +last_updated: '2026-07-30' +last_verified: '2026-07-30' review_cadence_days: 30 status: stable tags: [about, documentation, maintenance, automation] @@ -29,8 +29,14 @@ generated facts, examples, and operational claims must agree with source. - Release behavior: `.github/workflows/release.yaml`, `.github/workflows/infra-release.yaml`, and the machine-readable supply-chain policies. -- Program state and deferred work: - [tracker #324](https://github.com/bsv-blockchain/ts-stack/issues/324). +- Contribution policy: the root + [`CONTRIBUTING.md`](https://github.com/bsv-blockchain/ts-stack/blob/main/CONTRIBUTING.md). +- Deferred final QA: + [issue #400](https://github.com/bsv-blockchain/ts-stack/issues/400); + release/external assurance: + [issue #401](https://github.com/bsv-blockchain/ts-stack/issues/401); and + analysis/CI follow-up: + [issue #402](https://github.com/bsv-blockchain/ts-stack/issues/402). Do not copy source-manifest tables into prose. Run `pnpm docs:facts` and link to [Generated Stack Facts](../reference/stack-facts.md). @@ -152,6 +158,6 @@ A documentation PR should state: - runtime, deployment, security, or migration claims changed; and - intentionally historical or deferred content left unchanged. -Update [tracker #324](https://github.com/bsv-blockchain/ts-stack/issues/324) -when the work completes or changes the remaining program, so the issue and -repository do not diverge. +Update the applicable focused issue when work changes its remaining scope. Do +not reopen the retired modernization tracker or copy active policy into a +package-local document. diff --git a/docs/packages/helpers/templates.md b/docs/packages/helpers/templates.md index b362fdc28..063347d83 100644 --- a/docs/packages/helpers/templates.md +++ b/docs/packages/helpers/templates.md @@ -3,7 +3,7 @@ id: pkg-templates title: '@bsv/templates' kind: package domain: helpers -version: '1.9.5' +version: '1.9.6' source_repo: 'bsv-blockchain/ts-stack' last_updated: '2026-07-30' last_verified: '2026-07-30' diff --git a/docs/packages/sdk/bsv-sdk.md b/docs/packages/sdk/bsv-sdk.md index bdf41b06b..a21adbca5 100644 --- a/docs/packages/sdk/bsv-sdk.md +++ b/docs/packages/sdk/bsv-sdk.md @@ -3,7 +3,7 @@ id: bsv-sdk title: '@bsv/sdk' kind: package domain: sdk -version: '2.2.14' +version: '2.2.15' npm: '@bsv/sdk' last_updated: '2026-07-30' last_verified: '2026-07-30' diff --git a/docs/packages/wallet/wallet-toolbox-client.md b/docs/packages/wallet/wallet-toolbox-client.md index 9c4143d92..d780bb16d 100644 --- a/docs/packages/wallet/wallet-toolbox-client.md +++ b/docs/packages/wallet/wallet-toolbox-client.md @@ -3,7 +3,7 @@ id: pkg-wallet-toolbox-client title: '@bsv/wallet-toolbox-client' kind: package domain: wallet -version: '2.4.19' +version: '2.4.20' last_updated: '2026-07-30' last_verified: '2026-07-30' review_cadence_days: 30 diff --git a/docs/packages/wallet/wallet-toolbox-mobile.md b/docs/packages/wallet/wallet-toolbox-mobile.md index 31c031c82..a90ec0cd5 100644 --- a/docs/packages/wallet/wallet-toolbox-mobile.md +++ b/docs/packages/wallet/wallet-toolbox-mobile.md @@ -3,7 +3,7 @@ id: pkg-wallet-toolbox-mobile title: '@bsv/wallet-toolbox-mobile' kind: package domain: wallet -version: '2.4.19' +version: '2.4.20' last_updated: '2026-07-30' last_verified: '2026-07-30' review_cadence_days: 30 diff --git a/docs/packages/wallet/wallet-toolbox.md b/docs/packages/wallet/wallet-toolbox.md index ac8e2694f..a975dda17 100644 --- a/docs/packages/wallet/wallet-toolbox.md +++ b/docs/packages/wallet/wallet-toolbox.md @@ -4,7 +4,7 @@ title: '@bsv/wallet-toolbox' kind: package domain: wallet npm: '@bsv/wallet-toolbox' -version: '2.4.19' +version: '2.4.20' last_updated: '2026-07-30' last_verified: '2026-07-30' review_cadence_days: 30 diff --git a/docs/reference/package-api-migrations.md b/docs/reference/package-api-migrations.md index 9bc09d2a2..5edde9a3d 100644 --- a/docs/reference/package-api-migrations.md +++ b/docs/reference/package-api-migrations.md @@ -44,16 +44,16 @@ and clean-consumer tests remain the executable type authority. | `@bsv/overlay-topics` | `1.6.1` | `1.6.8` | patch | [API and usage](../packages/overlays/overlay-topics.md) | No consumer migration is required; topic IDs, lookup contracts, and persisted formats are unchanged. | | `@bsv/paymail` | `2.4.2` | `2.4.5` | patch | [API and usage](../packages/messaging/paymail.md) | No consumer migration is required; existing Paymail client APIs and protocol semantics are retained. | | `@bsv/payment-express-middleware` | `2.1.1` | `2.1.4` | patch | [API and usage](../packages/middleware/payment-express-middleware.md) | No consumer migration is required; legacy x-bsv-payment JSON behavior remains supported. | -| `@bsv/sdk` | `2.2.0` | `2.2.14` | patch | [API and usage](../packages/sdk/bsv-sdk.md) | No consumer migration is required; the source candidate preserves the 2.x public API, wire encodings, script semantics, errors, and supported import forms. | +| `@bsv/sdk` | `2.2.0` | `2.2.15` | patch | [API and usage](../packages/sdk/bsv-sdk.md) | No consumer migration is required; the source candidate preserves the 2.x public API, wire encodings, script semantics, errors, and supported import forms. | | `@bsv/simple` | `0.4.1` | `0.4.8` | patch | [API and usage](../packages/helpers/simple.md) | No consumer migration is required; the browser and server entry points remain compatible. | -| `@bsv/templates` | `1.9.1` | `1.9.5` | patch | [API and usage](../packages/helpers/templates.md) | No consumer migration is required; template APIs, supported imports, and generated script semantics are unchanged. | +| `@bsv/templates` | `1.9.1` | `1.9.6` | patch | [API and usage](../packages/helpers/templates.md) | No consumer migration is required; template APIs, supported imports, and generated script semantics are unchanged. | | `@bsv/teranode-listener` | `1.1.1` | `1.1.4` | patch | [API and usage](../packages/network/teranode-listener.md) | No consumer migration is required; listener APIs, topics, and network configuration are unchanged. | | `@bsv/verifast` | `0.3.0` | `0.3.4` | patch | [API and usage](../packages/sdk/verifast.md) | No consumer migration is required; exports, verification behavior, worker protocols, package paths, and runtime defaults are unchanged. | | `@bsv/wallet-helper` | `0.1.1` | `0.1.6` | patch | [API and usage](../packages/helpers/wallet-helper.md) | No consumer migration is required; fluent builder APIs and transaction semantics are unchanged. | | `@bsv/wallet-relay` | `0.2.2` | `0.3.3` | minor | [API and usage](../packages/wallet/wallet-relay.md) | QRPairingCode now renders a native button and accepts button wrapper attributes. Existing className, style, data, and ARIA props continue to work; update div-specific wrapper selectors or explicitly typed div event handlers. | -| `@bsv/wallet-toolbox` | `2.4.4` | `2.4.19` | patch | [API and usage](../packages/wallet/wallet-toolbox.md) | No consumer migration is required; persisted schemas, provider behavior, transaction construction, monitor behavior, and the 2.x wallet and storage interfaces remain compatible. | -| `@bsv/wallet-toolbox-client` | `2.4.4` | `2.4.19` | patch | [API and usage](../packages/wallet/wallet-toolbox-client.md) | No consumer migration is required; client entry points and remote storage contracts remain compatible. | -| `@bsv/wallet-toolbox-mobile` | `2.4.4` | `2.4.19` | patch | [API and usage](../packages/wallet/wallet-toolbox-mobile.md) | No consumer migration is required; React Native and mobile bridge contracts remain compatible. | +| `@bsv/wallet-toolbox` | `2.4.4` | `2.4.20` | patch | [API and usage](../packages/wallet/wallet-toolbox.md) | No consumer migration is required; persisted schemas, provider behavior, transaction construction, monitor behavior, and the 2.x wallet and storage interfaces remain compatible. | +| `@bsv/wallet-toolbox-client` | `2.4.4` | `2.4.20` | patch | [API and usage](../packages/wallet/wallet-toolbox-client.md) | No consumer migration is required; client entry points and remote storage contracts remain compatible. | +| `@bsv/wallet-toolbox-mobile` | `2.4.4` | `2.4.20` | patch | [API and usage](../packages/wallet/wallet-toolbox-mobile.md) | No consumer migration is required; React Native and mobile bridge contracts remain compatible. | | `create-bsv-app` | `1.0.2` | `1.0.4` | patch | [API and usage](../packages/helpers/create-bsv-app.md) | No consumer migration is required; generated application structure and CLI behavior are unchanged. | `none` means the source manifest matches the recorded npm baseline. Any other @@ -304,7 +304,7 @@ CLI entry points: `{"fund-metanet":"./dist/index.mjs"}`. - Package documentation: [docs/packages/sdk/bsv-sdk.md](../packages/sdk/bsv-sdk.md) - Source: [packages/sdk](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/sdk) -- Release note: Accumulates security and correctness hardening, transaction and action-batch performance work, strict package contracts, safer text and telemetry handling, and behavior-preserving maintainability remediation across cryptographic, transaction, remittance, lookup, script-interpreter, broadcaster, and wallet-wire paths. +- Release note: Accumulates security and correctness hardening, transaction and action-batch performance work, strict package contracts, safer text and telemetry handling, behavior-preserving maintainability remediation across cryptographic, transaction, remittance, lookup, script-interpreter, broadcaster, and wallet-wire paths, and canonical root contribution guidance. - Migration: No consumer migration is required; the source candidate preserves the 2.x public API, wire encodings, script semantics, errors, and supported import forms. | Public subpath | Runtime target(s) | Declaration target(s) | @@ -399,7 +399,7 @@ CLI entry points: `{"fund-metanet":"./dist/index.mjs"}`. - Package documentation: [docs/packages/helpers/templates.md](../packages/helpers/templates.md) - Source: [packages/helpers/ts-templates](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/helpers/ts-templates) -- Release note: Consolidates MultiPushDrop script construction while preserving the exact generated locking-script sequence, and adds an exact-tarball browser-consumer and bundle-size contract. +- Release note: Consolidates MultiPushDrop script construction while preserving the exact generated locking-script sequence, adds an exact-tarball browser-consumer and bundle-size contract, and points contributors to the canonical root policy. - Migration: No consumer migration is required; template APIs, supported imports, and generated script semantics are unchanged. | Public subpath | Runtime target(s) | Declaration target(s) | @@ -465,7 +465,7 @@ CLI entry points: `{"wallet-relay":"./bin/init.mjs"}`. - Package documentation: [docs/packages/wallet/wallet-toolbox.md](../packages/wallet/wallet-toolbox.md) - Source: [packages/wallet/wallet-toolbox](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/wallet/wallet-toolbox) -- Release note: Adds atomic action-batch transport, storage and proof resilience, security hardening, compatible runtime dependency maintenance, strict package contracts, broad maintainability remediation, explicit manual-suite governance, safe operator/example tooling, and redacted diagnostic summaries across wallet, monitor, provider, action-batch, storage, proof, and Chaintracks paths. +- Release note: Adds atomic action-batch transport, storage and proof resilience, security hardening, compatible runtime dependency maintenance, strict package contracts, broad maintainability remediation, explicit manual-suite governance, safe operator/example tooling, redacted diagnostic summaries across wallet, monitor, provider, action-batch, storage, proof, and Chaintracks paths, and canonical root contribution guidance. - Migration: No consumer migration is required; persisted schemas, provider behavior, transaction construction, monitor behavior, and the 2.x wallet and storage interfaces remain compatible. | Public subpath | Runtime target(s) | Declaration target(s) | @@ -479,7 +479,7 @@ CLI entry points: `{"wallet-relay":"./bin/init.mjs"}`. - Package documentation: [docs/packages/wallet/wallet-toolbox-client.md](../packages/wallet/wallet-toolbox-client.md) - Source: [packages/wallet/wallet-toolbox/client](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/wallet/wallet-toolbox/client) -- Release note: Carries the lockstep Wallet Toolbox client candidate with transport, validation, storage, provider, dependency, package, declaration, test, and operator-procedure hardening. +- Release note: Carries the lockstep Wallet Toolbox client candidate with transport, validation, storage, provider, dependency, package, declaration, test, operator-procedure hardening, and the synchronized contributor-policy release boundary. - Migration: No consumer migration is required; client entry points and remote storage contracts remain compatible. | Public subpath | Runtime target(s) | Declaration target(s) | @@ -491,7 +491,7 @@ CLI entry points: `{"wallet-relay":"./bin/init.mjs"}`. - Package documentation: [docs/packages/wallet/wallet-toolbox-mobile.md](../packages/wallet/wallet-toolbox-mobile.md) - Source: [packages/wallet/wallet-toolbox/mobile](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/wallet/wallet-toolbox/mobile) -- Release note: Carries the lockstep Wallet Toolbox mobile candidate with platform, React Native preset, transport, storage, provider, package, declaration, test, and operator-procedure hardening. +- Release note: Carries the lockstep Wallet Toolbox mobile candidate with platform, React Native preset, transport, storage, provider, package, declaration, test, operator-procedure hardening, and the synchronized contributor-policy release boundary. - Migration: No consumer migration is required; React Native and mobile bridge contracts remain compatible. | Public subpath | Runtime target(s) | Declaration target(s) | diff --git a/docs/reference/release-operations.md b/docs/reference/release-operations.md index dfcf877f8..2fff470be 100644 --- a/docs/reference/release-operations.md +++ b/docs/reference/release-operations.md @@ -214,7 +214,8 @@ private GitHub advisory, and coordinate disclosure under A release is complete only when source, published artifacts, post-release dependency state, deployed image digests where applicable, and documentation -agree. Update [tracker #324](https://github.com/bsv-blockchain/ts-stack/issues/324) +agree. Update +[release issue #401](https://github.com/bsv-blockchain/ts-stack/issues/401) with the exact evidence; do not mark package availability or deployed behavior complete from a local build alone. diff --git a/docs/reference/repository-health.md b/docs/reference/repository-health.md index 073919d46..7c68152e8 100644 --- a/docs/reference/repository-health.md +++ b/docs/reference/repository-health.md @@ -2,7 +2,7 @@ id: repository-health title: 'Repository Health Controls' kind: reference -version: '1.3.3' +version: '1.4.0' last_updated: '2026-07-30' last_verified: '2026-07-30' review_cadence_days: 30 @@ -13,8 +13,14 @@ tags: [reference, governance, quality, security, releases] # Repository Health Controls The repository health controls turn the TypeScript stack maintenance program -into a checked, machine-readable contract. The authoritative program tracker is -[GitHub issue #324](https://github.com/bsv-blockchain/ts-stack/issues/324). +into a checked, machine-readable contract. Root +[`CONTRIBUTING.md`](https://github.com/bsv-blockchain/ts-stack/blob/main/CONTRIBUTING.md), +`AGENTS.md`, governance registries, and CI are the current authority. Retired +[tracker #324](https://github.com/bsv-blockchain/ts-stack/issues/324) +preserves modernization history; focused remaining work is tracked in +[QA #400](https://github.com/bsv-blockchain/ts-stack/issues/400), +[release/assurance #401](https://github.com/bsv-blockchain/ts-stack/issues/401), +and [analysis/CI #402](https://github.com/bsv-blockchain/ts-stack/issues/402). ## Sources of truth @@ -220,6 +226,21 @@ The job writes a rule-by-rule and project-by-project report to the GitHub Action step summary and feeds the required `merge-gate`. Known findings stay visible until their remediation PR removes them from both the code and baseline. +## Contributor-policy control + +`governance/contributor-policy.json` makes the root contribution and agent +doctrine executable. `pnpm contributor-policy:check` requires a generated +root-policy pointer at all 37 governed workspace projects and seven standalone +services (43 unique nested scopes), forbids nested `.github` ownership and +package-local contribution policies, verifies root templates/workflows, and +retains the disposition of 31 legacy agent guides, 49 imported GitHub files, +and eight superseded package policies. `pnpm contributor-policy:sync` +regenerates pointers after a project or service is added. + +The root CI repository-health job runs this check before installed workspace +jobs. A new package or service cannot silently invent different lint, review, +release, dependency, documentation, migration, or agent rules. + ## Completion discipline A program checkbox is complete only after the corresponding code is merged and diff --git a/docs/reference/stack-facts.md b/docs/reference/stack-facts.md index 9fca11101..30564224e 100644 --- a/docs/reference/stack-facts.md +++ b/docs/reference/stack-facts.md @@ -42,7 +42,7 @@ authorized release action. | helpers | `@bsv/did-client` | `1.2.3` | browser-library | browser-bundler, browser-esm, node-cjs, node-esm, umd-global | browser, node, umd | `>=22` | [packages/helpers/did-client](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/helpers/did-client) | | helpers | `@bsv/fund-wallet` | `1.4.3` | cli | cli | node | `>=22` | [packages/helpers/fund-wallet](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/helpers/fund-wallet) | | helpers | `@bsv/simple` | `0.4.8` | browser-library | browser-bundler, browser-esm, node-cjs, node-esm | browser, node | `>=22` | [packages/helpers/simple](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/helpers/simple) | -| helpers | `@bsv/templates` | `1.9.5` | browser-library | browser-bundler, browser-esm, node-cjs, node-esm | browser, node | `>=22` | [packages/helpers/ts-templates](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/helpers/ts-templates) | +| helpers | `@bsv/templates` | `1.9.6` | browser-library | browser-bundler, browser-esm, node-cjs, node-esm | browser, node | `>=22` | [packages/helpers/ts-templates](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/helpers/ts-templates) | | helpers | `@bsv/wallet-helper` | `0.1.6` | node-library | node-cjs, node-esm | node | `>=22` | [packages/helpers/bsv-wallet-helper](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/helpers/bsv-wallet-helper) | | helpers | `create-bsv-app` | `1.0.4` | cli | cli | node | `>=22` | [packages/helpers/create-bsv-app](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/helpers/create-bsv-app) | | messaging | `@bsv/authsocket` | `2.1.4` | node-library | node-cjs, node-esm | node | `>=22` | [packages/messaging/authsocket](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/messaging/authsocket) | @@ -59,14 +59,14 @@ authorized release action. | overlays | `@bsv/overlay-discovery-services` | `2.1.6` | node-library | node-cjs, node-esm | node | `>=22` | [packages/overlays/overlay-discovery-services](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/overlays/overlay-discovery-services) | | overlays | `@bsv/overlay-express` | `2.4.9` | node-library | node-cjs, node-esm | node | `>=22` | [packages/overlays/overlay-express](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/overlays/overlay-express) | | overlays | `@bsv/overlay-topics` | `1.6.8` | node-library | node-esm | node | `>=22` | [packages/overlays/topics](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/overlays/topics) | -| sdk | `@bsv/sdk` | `2.2.14` | browser-library | browser-bundler, browser-esm, node-cjs, node-esm, umd-global | browser, node, umd | `>=22` | [packages/sdk](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/sdk) | +| sdk | `@bsv/sdk` | `2.2.15` | browser-library | browser-bundler, browser-esm, node-cjs, node-esm, umd-global | browser, node, umd | `>=22` | [packages/sdk](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/sdk) | | sdk | `@bsv/verifast` | `0.3.4` | wasm-library | browser-bundler, browser-esm, node-cjs, node-esm, umd-global, wasm-worker | browser, node, umd, wasm, worker | `>=22` | [packages/verifast](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/verifast) | | wallet | `@bsv/btms` | `1.1.4` | node-library | node-cjs, node-esm | node | `>=22` | [packages/wallet/btms](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/wallet/btms) | | wallet | `@bsv/btms-permission-module` | `1.1.3` | node-library | node-esm | node | `>=22` | [packages/wallet/btms-permission-module](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/wallet/btms-permission-module) | | wallet | `@bsv/wallet-relay` | `0.3.3` | cli-library | browser-bundler, browser-esm, cli, node-cjs, node-esm | browser, node | `>=22` | [packages/wallet/ts-wallet-relay](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/wallet/ts-wallet-relay) | -| wallet | `@bsv/wallet-toolbox` | `2.4.19` | node-library | node-cjs | node | `>=22` | [packages/wallet/wallet-toolbox](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/wallet/wallet-toolbox) | -| wallet | `@bsv/wallet-toolbox-client` | `2.4.19` | browser-library | browser-bundler, browser-esm, node-cjs, node-esm | browser, node | `>=22` | [packages/wallet/wallet-toolbox/client](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/wallet/wallet-toolbox/client) | -| wallet | `@bsv/wallet-toolbox-mobile` | `2.4.19` | react-native-library | react-native-metro | react-native | `>=22` | [packages/wallet/wallet-toolbox/mobile](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/wallet/wallet-toolbox/mobile) | +| wallet | `@bsv/wallet-toolbox` | `2.4.20` | node-library | node-cjs | node | `>=22` | [packages/wallet/wallet-toolbox](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/wallet/wallet-toolbox) | +| wallet | `@bsv/wallet-toolbox-client` | `2.4.20` | browser-library | browser-bundler, browser-esm, node-cjs, node-esm | browser, node | `>=22` | [packages/wallet/wallet-toolbox/client](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/wallet/wallet-toolbox/client) | +| wallet | `@bsv/wallet-toolbox-mobile` | `2.4.20` | react-native-library | react-native-metro | react-native | `>=22` | [packages/wallet/wallet-toolbox/mobile](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/wallet/wallet-toolbox/mobile) | ## Standalone infrastructure manifests diff --git a/docs/specs/index.md b/docs/specs/index.md index 7d44a7a32..460acf785 100644 --- a/docs/specs/index.md +++ b/docs/specs/index.md @@ -85,6 +85,8 @@ The authoritative BRC repository is at [github.com/bitcoin-sv/BRCs](https://gith ## Related Documentation -- **Implementations** — See package-specific guides in `/packages/*/CLAUDE.md` +- **Implementations** — See package READMEs and package pages under + `/docs/packages/`; contributor and agent policy is owned by root + `CONTRIBUTING.md` and `AGENTS.md` - **Error Codes** — Reference `specs/errors.md` for standardized error responses - **Network Services** — Consult `/docs/network-services/` for Merkle Service, ARC endpoints, and provider lists diff --git a/governance/contributor-policy.json b/governance/contributor-policy.json new file mode 100644 index 000000000..bb57cdee4 --- /dev/null +++ b/governance/contributor-policy.json @@ -0,0 +1,171 @@ +{ + "schemaVersion": 1, + "owner": "ts-stack-maintainers", + "lastReviewed": "2026-07-30", + "authority": { + "agentInstructions": "AGENTS.md", + "contributionGuide": "CONTRIBUTING.md", + "codeOfConduct": "CODE_OF_CONDUCT.md", + "securityPolicy": ".github/SECURITY.md", + "pullRequestTemplate": ".github/pull_request_template.md", + "issueTemplateDirectory": ".github/ISSUE_TEMPLATE" + }, + "scopeSources": [ + "governance/repository-health/projects.json", + "governance/service-operations.json" + ], + "packageAgentPolicy": { + "mode": "generated-pointer-only", + "syncCommand": "pnpm contributor-policy:sync", + "technicalDocumentationLocations": ["README.md", "docs/", "specs/", "governance/"] + }, + "legacyAgentConsolidation": { + "decision": "replaced-with-generated-root-pointers", + "replacement": "AGENTS.md, CONTRIBUTING.md, current package READMEs, generated package documentation, specifications, and operator guides", + "rationale": "The former package files mixed contributor rules with duplicated API inventories, stale package versions, obsolete commands, and generated AI-oriented notes. Current technical authorities already live in package READMEs, generated package documentation, specifications, service contracts, and operator guides; copying stale inventories forward would create a second source of truth. Each former file is now a generated pointer to the root doctrine.", + "priorFiles": [ + "infra/message-box-server/AGENTS.md", + "infra/overlay-server/AGENTS.md", + "infra/uhrp-server-basic/AGENTS.md", + "infra/uhrp-server-cloud-bucket/AGENTS.md", + "infra/wab/AGENTS.md", + "infra/wallet-infra/AGENTS.md", + "packages/helpers/amountinator/AGENTS.md", + "packages/helpers/bsv-wallet-helper/AGENTS.md", + "packages/helpers/did-client/AGENTS.md", + "packages/helpers/fund-wallet/AGENTS.md", + "packages/helpers/simple/AGENTS.md", + "packages/helpers/ts-templates/AGENTS.md", + "packages/messaging/authsocket-client/AGENTS.md", + "packages/messaging/authsocket/AGENTS.md", + "packages/messaging/message-box-client/AGENTS.md", + "packages/messaging/ts-paymail/AGENTS.md", + "packages/middleware/402-pay/AGENTS.md", + "packages/middleware/auth-express-middleware/AGENTS.md", + "packages/middleware/payment-express-middleware/AGENTS.md", + "packages/network/ts-p2p/AGENTS.md", + "packages/overlays/btms-backend/AGENTS.md", + "packages/overlays/gasp-core/AGENTS.md", + "packages/overlays/overlay-discovery-services/AGENTS.md", + "packages/overlays/overlay-express/AGENTS.md", + "packages/overlays/overlay/AGENTS.md", + "packages/overlays/topics/AGENTS.md", + "packages/sdk/AGENTS.md", + "packages/wallet/btms-permission-module/AGENTS.md", + "packages/wallet/btms/AGENTS.md", + "packages/wallet/ts-wallet-relay/AGENTS.md", + "packages/wallet/wallet-toolbox/AGENTS.md" + ] + }, + "prohibitedNestedPolicyFiles": [ + "CONTRIBUTING.md", + "CODE_OF_CONDUCT.md", + "CLAUDE.md", + "GEMINI.md", + ".cursorrules" + ], + "requiredRootWorkflows": [ + ".github/workflows/ci.yml", + ".github/workflows/codeql.yml", + ".github/workflows/conformance.yml", + ".github/workflows/dependency-release-verification.yml", + ".github/workflows/docs-deploy.yml", + ".github/workflows/infra-release.yaml", + ".github/workflows/release.yaml", + ".github/workflows/wab-marketplace-release.yml" + ], + "requiredPullRequestEvidence": [ + "exact-head", + "zero new Sonar findings", + "CodeQL", + "self-review", + "Documentation, changelog, migration", + "Review conversations are resolved", + "all applicable checks" + ], + "historicalGitHubDispositions": [ + { + "id": "standalone-package-build-test-publish", + "decision": "consolidated", + "replacement": ".github/workflows/ci.yml and .github/workflows/release.yaml", + "rationale": "Root CI retains build, strict lint, typecheck, tests, coverage, package consumers, browser/mobile, conformance, Sonar, and security gates. Root release replaces merge-to-main npm publication with reviewed, immutable, scanned, attested OIDC candidates.", + "files": [ + "packages/helpers/amountinator/.github/workflows/push.yaml", + "packages/helpers/bsv-wallet-helper/.github/workflows/push.yaml", + "packages/helpers/did-client/.github/workflows/push.yaml", + "packages/helpers/simple/.github/workflows/push.yaml", + "packages/helpers/ts-templates/.github/workflows/push.yaml", + "packages/messaging/authsocket-client/.github/workflows/push.yaml", + "packages/messaging/authsocket/.github/workflows/push.yaml", + "packages/messaging/ts-paymail/.github/workflows/push.yaml", + "packages/middleware/402-pay/.github/workflows/push.yaml", + "packages/middleware/auth-express-middleware/.github/workflows/push.yaml", + "packages/middleware/payment-express-middleware/.github/workflows/push.yaml", + "packages/network/ts-p2p/.github/workflows/push.yaml", + "packages/overlays/gasp-core/.github/workflows/push.yaml", + "packages/overlays/overlay-discovery-services/.github/workflows/push.yaml", + "packages/overlays/overlay-express/.github/workflows/push.yaml", + "packages/overlays/overlay/.github/workflows/push.yaml", + "packages/wallet/wallet-toolbox/.github/workflows/push.yaml" + ] + }, + { + "id": "standalone-community-templates", + "decision": "consolidated", + "replacement": ".github/ISSUE_TEMPLATE and .github/pull_request_template.md", + "rationale": "The root forms retain reproducibility, environment, expected/actual behavior, discussion, tests, self-review, documentation, changelog, and migration intent while adding stack scope, compatibility, security, dependency, exact-head, and quality evidence.", + "files": [ + "packages/helpers/bsv-wallet-helper/.github/ISSUE_TEMPLATE/bug_report.md", + "packages/helpers/bsv-wallet-helper/.github/ISSUE_TEMPLATE/discussion.md", + "packages/helpers/bsv-wallet-helper/.github/pull_request_template.md", + "packages/helpers/simple/.github/ISSUE_TEMPLATE/bug_report.md", + "packages/helpers/simple/.github/ISSUE_TEMPLATE/discussion.md", + "packages/helpers/simple/.github/pull_request_template.md", + "packages/messaging/ts-paymail/.github/ISSUE_TEMPLATE/bug_report.md", + "packages/messaging/ts-paymail/.github/ISSUE_TEMPLATE/discussion.md", + "packages/messaging/ts-paymail/.github/pull_request_template.md", + "packages/network/ts-p2p/.github/ISSUE_TEMPLATE/bug_report.md", + "packages/network/ts-p2p/.github/ISSUE_TEMPLATE/discussion.md", + "packages/network/ts-p2p/.github/pull_request_template.md", + "packages/overlays/overlay-express/.github/ISSUE_TEMPLATE/bug_report.md", + "packages/overlays/overlay-express/.github/ISSUE_TEMPLATE/discussion.md", + "packages/overlays/overlay-express/.github/pull_request_template.md", + "packages/overlays/overlay/.github/ISSUE_TEMPLATE/bug_report.md", + "packages/overlays/overlay/.github/ISSUE_TEMPLATE/discussion.md", + "packages/overlays/overlay/.github/pull_request_template.md", + "packages/wallet/wallet-toolbox/.github/ISSUE_TEMPLATE/bug_report.md", + "packages/wallet/wallet-toolbox/.github/ISSUE_TEMPLATE/discussion.md", + "packages/wallet/wallet-toolbox/.github/pull_request_template.md" + ] + }, + { + "id": "standalone-infrastructure-automation", + "decision": "consolidated-and-retired", + "replacement": ".github/workflows/infra-release.yaml, .github/workflows/wab-marketplace-release.yml, .github/dependabot.yml, and operator-owned deployment systems", + "rationale": "Root workflows retain Linux/amd64 image builds, vulnerability scans, SBOMs, provenance, signatures, immutable GHCR publication, post-release verification, and the complete WAB Marketplace transaction. Inert Cloud Run/bootstrap examples, static cloud keys, hard-coded projects, legacy Docker Hub pushes, branch-based deployments, and ECR-only uploads without governed listing registration are unsafe operational history and are not reactivated.", + "files": [ + "infra/message-box-server/.github/workflows/build-and-push.yml", + "infra/message-box-server/.github/workflows/deploy.yaml.example", + "infra/message-box-server/.github/workflows/image.yaml", + "infra/overlay-server/.github/workflows/image.yaml", + "infra/uhrp-server-cloud-bucket/.github/dependabot.yml", + "infra/uhrp-server-cloud-bucket/.github/workflows/build.yaml", + "infra/uhrp-server-cloud-bucket/.github/workflows/deploy.yaml", + "infra/uhrp-server-cloud-bucket/.github/workflows/setup.yaml", + "infra/wab/.github/workflows/deploy.yaml.example", + "infra/wallet-infra/.github/workflows/build-and-push.yml", + "infra/wallet-infra/.github/workflows/build.yaml" + ] + } + ], + "retiredPackageContributionFiles": [ + "packages/helpers/ts-templates/CONTRIBUTING.md", + "packages/messaging/ts-paymail/CODE_OF_CONDUCT.md", + "packages/messaging/ts-paymail/CONTRIBUTING.md", + "packages/network/ts-p2p/CODE_OF_CONDUCT.md", + "packages/network/ts-p2p/CONTRIBUTING.md", + "packages/overlays/overlay/CONTRIBUTING.md", + "packages/sdk/CONTRIBUTING.md", + "packages/wallet/wallet-toolbox/CONTRIBUTING.md" + ] +} diff --git a/governance/documentation-policy.json b/governance/documentation-policy.json index cff8ab19a..5b3f711ae 100644 --- a/governance/documentation-policy.json +++ b/governance/documentation-policy.json @@ -2,7 +2,13 @@ "schemaVersion": 1, "owner": "ts-stack-maintainers", "lastReviewed": "2026-07-30", - "tracker": "https://github.com/bsv-blockchain/ts-stack/issues/324", + "programHistory": "https://github.com/bsv-blockchain/ts-stack/issues/324", + "contributionAuthority": "CONTRIBUTING.md", + "activeFollowUps": { + "qualityAssurance": "https://github.com/bsv-blockchain/ts-stack/issues/400", + "releaseAndExternalAssurance": "https://github.com/bsv-blockchain/ts-stack/issues/401", + "analysisAndCiEfficiency": "https://github.com/bsv-blockchain/ts-stack/issues/402" + }, "publicPackageReadme": { "requiredSections": ["installation", "usage", "license"], "requiredLicenseFile": "LICENSE.txt", diff --git a/governance/package-release-notes.json b/governance/package-release-notes.json index 2d1357593..9f65e5085 100644 --- a/governance/package-release-notes.json +++ b/governance/package-release-notes.json @@ -140,7 +140,7 @@ "name": "@bsv/sdk", "publishedVersion": "2.2.0", "releaseType": "patch", - "summary": "Accumulates security and correctness hardening, transaction and action-batch performance work, strict package contracts, safer text and telemetry handling, and behavior-preserving maintainability remediation across cryptographic, transaction, remittance, lookup, script-interpreter, broadcaster, and wallet-wire paths.", + "summary": "Accumulates security and correctness hardening, transaction and action-batch performance work, strict package contracts, safer text and telemetry handling, behavior-preserving maintainability remediation across cryptographic, transaction, remittance, lookup, script-interpreter, broadcaster, and wallet-wire paths, and canonical root contribution guidance.", "migration": "No consumer migration is required; the source candidate preserves the 2.x public API, wire encodings, script semantics, errors, and supported import forms." }, { @@ -154,7 +154,7 @@ "name": "@bsv/templates", "publishedVersion": "1.9.1", "releaseType": "patch", - "summary": "Consolidates MultiPushDrop script construction while preserving the exact generated locking-script sequence, and adds an exact-tarball browser-consumer and bundle-size contract.", + "summary": "Consolidates MultiPushDrop script construction while preserving the exact generated locking-script sequence, adds an exact-tarball browser-consumer and bundle-size contract, and points contributors to the canonical root policy.", "migration": "No consumer migration is required; template APIs, supported imports, and generated script semantics are unchanged." }, { @@ -189,21 +189,21 @@ "name": "@bsv/wallet-toolbox", "publishedVersion": "2.4.4", "releaseType": "patch", - "summary": "Adds atomic action-batch transport, storage and proof resilience, security hardening, compatible runtime dependency maintenance, strict package contracts, broad maintainability remediation, explicit manual-suite governance, safe operator/example tooling, and redacted diagnostic summaries across wallet, monitor, provider, action-batch, storage, proof, and Chaintracks paths.", + "summary": "Adds atomic action-batch transport, storage and proof resilience, security hardening, compatible runtime dependency maintenance, strict package contracts, broad maintainability remediation, explicit manual-suite governance, safe operator/example tooling, redacted diagnostic summaries across wallet, monitor, provider, action-batch, storage, proof, and Chaintracks paths, and canonical root contribution guidance.", "migration": "No consumer migration is required; persisted schemas, provider behavior, transaction construction, monitor behavior, and the 2.x wallet and storage interfaces remain compatible." }, { "name": "@bsv/wallet-toolbox-client", "publishedVersion": "2.4.4", "releaseType": "patch", - "summary": "Carries the lockstep Wallet Toolbox client candidate with transport, validation, storage, provider, dependency, package, declaration, test, and operator-procedure hardening.", + "summary": "Carries the lockstep Wallet Toolbox client candidate with transport, validation, storage, provider, dependency, package, declaration, test, operator-procedure hardening, and the synchronized contributor-policy release boundary.", "migration": "No consumer migration is required; client entry points and remote storage contracts remain compatible." }, { "name": "@bsv/wallet-toolbox-mobile", "publishedVersion": "2.4.4", "releaseType": "patch", - "summary": "Carries the lockstep Wallet Toolbox mobile candidate with platform, React Native preset, transport, storage, provider, package, declaration, test, and operator-procedure hardening.", + "summary": "Carries the lockstep Wallet Toolbox mobile candidate with platform, React Native preset, transport, storage, provider, package, declaration, test, operator-procedure hardening, and the synchronized contributor-policy release boundary.", "migration": "No consumer migration is required; React Native and mobile bridge contracts remain compatible." }, { diff --git a/governance/repository-health/baselines.json b/governance/repository-health/baselines.json index b6d070adb..9df6efe99 100644 --- a/governance/repository-health/baselines.json +++ b/governance/repository-health/baselines.json @@ -304,7 +304,7 @@ "@bsv/did-client": "1.2.3", "@bsv/fund-wallet": "1.4.3", "@bsv/simple": "0.4.8", - "@bsv/templates": "1.9.5", + "@bsv/templates": "1.9.6", "@bsv/authsocket": "2.1.4", "@bsv/authsocket-client": "2.1.3", "@bsv/message-box-client": "2.2.6", @@ -319,13 +319,13 @@ "@bsv/overlay-discovery-services": "2.1.6", "@bsv/overlay-express": "2.4.9", "@bsv/overlay-topics": "1.6.8", - "@bsv/sdk": "2.2.14", + "@bsv/sdk": "2.2.15", "@bsv/verifast": "0.3.4", "@bsv/btms": "1.1.4", "@bsv/btms-permission-module": "1.1.3", "@bsv/wallet-relay": "0.3.3", - "@bsv/wallet-toolbox-client": "2.4.19", - "@bsv/wallet-toolbox-mobile": "2.4.19", - "@bsv/wallet-toolbox": "2.4.19" + "@bsv/wallet-toolbox-client": "2.4.20", + "@bsv/wallet-toolbox-mobile": "2.4.20", + "@bsv/wallet-toolbox": "2.4.20" } } diff --git a/infra/chaintracks-server/AGENTS.md b/infra/chaintracks-server/AGENTS.md new file mode 100644 index 000000000..da1c81f82 --- /dev/null +++ b/infra/chaintracks-server/AGENTS.md @@ -0,0 +1,10 @@ +# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](../../AGENTS.md) +and [contribution policy](../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/infra/message-box-server/.github/workflows/build-and-push.yml b/infra/message-box-server/.github/workflows/build-and-push.yml deleted file mode 100644 index 469a210d3..000000000 --- a/infra/message-box-server/.github/workflows/build-and-push.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Build and Push to AWS Marketplace ECR - -on: - workflow_dispatch: - inputs: - version: - description: 'Version tag (e.g., 1.0.0)' - required: true - default: '1.0.0' - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: us-east-1 - - - name: Login to Amazon ECR - run: | - aws ecr get-login-password --region us-east-1 | \ - docker login --username AWS --password-stdin \ - 709825985650.dkr.ecr.us-east-1.amazonaws.com - - - name: Build Docker image - run: | - docker build --platform linux/amd64 -t message-box-server:${{ github.event.inputs.version }} . - - - name: Tag image - run: | - docker tag message-box-server:${{ github.event.inputs.version }} \ - 709825985650.dkr.ecr.us-east-1.amazonaws.com/bsv-blockchain/message-box-server:${{ github.event.inputs.version }} - - - name: Push to ECR - run: | - docker push 709825985650.dkr.ecr.us-east-1.amazonaws.com/bsv-blockchain/message-box-server:${{ github.event.inputs.version }} - - - name: Verify push - run: | - aws ecr describe-images \ - --registry-id 709825985650 \ - --repository-name bsv-blockchain/message-box-server \ - --region us-east-1 \ - --image-ids imageTag=${{ github.event.inputs.version }} diff --git a/infra/message-box-server/.github/workflows/deploy.yaml.example b/infra/message-box-server/.github/workflows/deploy.yaml.example deleted file mode 100644 index fbd250652..000000000 --- a/infra/message-box-server/.github/workflows/deploy.yaml.example +++ /dev/null @@ -1,64 +0,0 @@ -name: Deployment -on: - push: - branches: - - master - - production - -env: - CURRENT_BRANCH: ${{ github.ref_name == 'production' && 'production' || 'master' }} - GCR_HOST: us.gcr.io - GOOGLE_PROJECT_ID: example-project-id - GCR_IMAGE_NAME: messagebox-server - -jobs: - build: - name: Deploy - runs-on: ubuntu-latest - steps: - - name: Check out code - uses: actions/checkout@v2 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: 22 - - - name: Install Dependencies - run: npm ci - - - name: Push Docker Image to GCR - uses: RafikFarhad/push-to-gcr-github-action@v4.1 - with: - gcloud_service_key: ${{ secrets.DOCKER_REGISTRY_PUSH_KEY }} - registry: ${{ env.GCR_HOST }} - project_id: ${{ env.GOOGLE_PROJECT_ID }} - image_name: ${{ env.GCR_IMAGE_NAME }} - image_tag: latest,${{ env.CURRENT_BRANCH }}-${{ github.sha }} - - - name: Create Service Description File - run: ./scripts/mkenv.sh service.${{ env.CURRENT_BRANCH }}.yaml - env: - IMAGE: "${{ env.GCR_HOST }}/${{ env.GOOGLE_PROJECT_ID }}/${{ env.GCR_IMAGE_NAME }}:${{ env.CURRENT_BRANCH }}-${{ github.sha }}" - SERVICE: ${{ env.CURRENT_BRANCH == 'production' && 'prod-messagebox-server' || 'staging-messagebox-server' }} - BSV_NETWORK: ${{ env.CURRENT_BRANCH == 'production' && 'mainnet' || 'testnet' }} - NODE_ENV: ${{ env.CURRENT_BRANCH =='production' && 'production' || 'staging' }} - WALLET_STORAGE_URL: ${{ env.CURRENT_BRANCH == 'production' && secrets.PROD_WALLET_STORAGE_URL || secrets.STAGING_WALLET_STORAGE_URL }} - SERVER_PRIVATE_KEY: ${{ env.CURRENT_BRANCH == 'production' && secrets.PROD_SERVER_PRIVATE_KEY || secrets.STAGING_SERVER_PRIVATE_KEY }} - KNEX_DB_CONNECTION: ${{ env.CURRENT_BRANCH == 'production' && secrets.PROD_KNEX_DB_CONNECTION || secrets.STAGING_KNEX_DB_CONNECTION }} - LOGGING_ENABLED: ${{ env.CURRENT_BRANCH == 'production' && secrets.PROD_LOGGING_ENABLED || secrets.STAGING_LOGGING_ENABLED }} - - name: Authenticate to Google Cloud - uses: google-github-actions/auth@v1 - with: - credentials_json: ${{ secrets.gcp_deploy_creds }} - - - name: Deploy to Cloud Run - uses: google-github-actions/deploy-cloudrun@v1 - with: - metadata: "service.${{ env.CURRENT_BRANCH }}.yaml" - region: us-west1 - - - name: Set Min Instances and Enable CPU Boost - run: | - gcloud run services update ${{ env.CURRENT_BRANCH == 'production' && 'prod-storage' || 'staging-storage' }} \ - --min-instances=1 --cpu-boost --region=us-west1 \ No newline at end of file diff --git a/infra/message-box-server/.github/workflows/image.yaml b/infra/message-box-server/.github/workflows/image.yaml deleted file mode 100644 index 6cc29cf34..000000000 --- a/infra/message-box-server/.github/workflows/image.yaml +++ /dev/null @@ -1,66 +0,0 @@ -name: Build and push OCI image to Docker Hub - -on: - push: - tags: - - 'v*' - -jobs: - check-current-branch: - runs-on: ubuntu-latest - outputs: - branch: ${{ steps.check_step.outputs.branch }} - steps: - - name: Checkout - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 - with: - fetch-depth: 0 - - - name: Get current branch - id: check_step - # 1. Get the list of branches ref where this tag exists - # 2. Remove 'origin/' from that result - # 3. Put that string in output - run: | - raw=$(git branch -r --contains ${{ github.ref }}) - branch="$(echo ${raw//origin\//} | tr -d '\n')" - echo "{name}=branch" >> $GITHUB_OUTPUT - echo "Branches where this tag exists : $branch." - - image: - runs-on: ubuntu-latest - needs: check-current-branch - if: contains(${{ needs.check.outputs.branch }}, 'main')` - steps: - - name: Check out the repo - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Get build args - id: build_args - run: | - echo "APP_COMMIT=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - echo "APP_VERSION=$(git describe --tags --always --abbrev=0 --match='v[0-9]*.[0-9]*.[0-9]*' 2> /dev/null | sed 's/^.//')" >> "$GITHUB_OUTPUT" - - - name: Log in to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) - id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 - with: - images: bsvb/message-box-server - - - name: Build and push image - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 - with: - context: . - file: ./Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - APP_COMMIT=${{ steps.build_args.outputs.APP_COMMIT }} - APP_VERSION=${{ steps.build_args.outputs.APP_VERSION }} diff --git a/infra/message-box-server/AGENTS.md b/infra/message-box-server/AGENTS.md index 45faa144d..da1c81f82 100644 --- a/infra/message-box-server/AGENTS.md +++ b/infra/message-box-server/AGENTS.md @@ -1,113 +1,10 @@ -# Message Box Server Maintainer Guide +# ts-stack agent instructions -## Scope +This project follows the repository-wide [agent instructions](../../AGENTS.md) +and [contribution policy](../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -`@bsv/messagebox-server` is the private deployable service behind -`@bsv/message-box-client`. It stores encrypted messages, enforces -recipient-controlled delivery permissions and fees, authenticates HTTP and -WebSocket peers with BRC-103, and optionally delivers Firebase notifications. -It is shipped as a container, not a public npm artifact. - -## Service contract - -Public pre-auth routes: - -- `GET /health` — process liveness -- `GET /ready` — non-sensitive database readiness -- `GET /docs` and `GET /openapi.json` — runtime documentation - -Authenticated routes: - -- `POST /sendMessage` — one to 100 recipients -- `POST /listMessages` — deterministic pages of at most 1,000 records -- `POST /acknowledgeMessage` — at most 1,000 IDs -- `POST /registerDevice` — an FCM token cannot be reassigned across identities -- `GET /devices` — redacted, bounded pagination -- `POST /permissions/set` -- `GET /permissions/get` -- `GET /permissions/list` — bounded pagination -- `GET /permissions/quote` — one to 100 recipients with bounded concurrency - -WebSocket rooms are `{identityKey}-{messageBox}`. Identity comes from the -authenticated transport, never solely from a payload claim. WebSocket sends -reuse the complete HTTP validation, permission, fee, payment, duplicate, and -persistence path. - -## Security and availability invariants - -- Message Box is a public protocol service. Default CORS is credential-free - wildcard access, including opaque `Origin: null`. Operators may explicitly - select an exact allowlist or disable cross-origin browser access. -- Do not use CORS or CSP as authentication. Preserve BRC-103 authentication, - recipient ownership, end-to-end encryption, permission/payment policy, - bounded work, and rate limits. -- Permission/fee storage failures fail closed. They must never become free or - allowed delivery. -- Implicit default permission reads do not write rows. Explicit box-wide and - sender-specific permissions are uniquely keyed by normalized sender scope. -- Log no service private keys, complete FCM tokens, auth material, payment - payloads, or plaintext message bodies. -- Database migrations, wallet/auth initialization, and WebSocket setup finish - before the process listens. A failed prerequisite is a failed process. -- `PORT` takes precedence over compatibility fallback `HTTP_PORT`; the default - is 8080. The container serves Node HTTP/WebSocket traffic directly. A trusted - platform ingress may sit in front of it. -- Horizontal WebSocket routing and the default rate-limit store are - process-local. Multiple replicas require sticky routing or an authenticated - shared broker/store. - -## Shared policy files - -`src/security/edgePolicy.ts` and `src/security/rateLimitPolicy.ts` are -byte-for-byte synchronized from the canonical WAB implementations. Do not make -message-box-only edits or reformat them. Change the canonical policy and run: - -```bash -pnpm sync:service-edge-policy -pnpm sync:service-rate-limit-policy -``` - -They are intentionally excluded from this service's local Prettier pass. - -## File map - -- `src/index.ts` — standalone migration/init/listen lifecycle -- `src/app.ts` — Express, auth, public edge policy, and route wiring -- `src/compose.ts`, `src/context.ts`, `src/runtimeDeps.ts` — embeddable API and - dependency binding -- `src/routes/` — HTTP handlers -- `src/security/` — shared edge/rate policy and Message Box WebSocket policy -- `src/migrations/` — Knex schema history -- `src/config/firebase.ts` — optional Firebase initialization -- `src/telemetry.ts`, `src/utils/logger.ts` — observability -- `Dockerfile` — digest-pinned Node 24 build/runtime -- `README.md`, `DEPLOYING.md` — developer and operator documentation -- `specs/messaging/message-box-http.yaml` — reviewed source contract - -## Required checks - -From `infra/message-box-server`: - -```bash -npm ci --ignore-scripts -npm rebuild better-sqlite3 -npm run typecheck -npm run format:check -npm run lint -npm test -npm run test:coverage -npm run build -npm audit --audit-level=high -``` - -From the repository root, also run synchronized-policy, OpenAPI/codegen, -documentation, repository-health, and Linux container CI gates. Do not rely on -a macOS image build as Linux/amd64 release evidence. - -## Deployment - -Use the repository's git-triggered release/deployment path and immutable image -evidence. Keep CORS mode, trusted proxy hops, database and wallet endpoints, -Firebase credentials, OTLP settings, probes, rollback, and migration evidence -under operator control. Never commit secrets. Do not bump or publish an npm -version as part of service maintenance. +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/infra/overlay-server/.github/workflows/image.yaml b/infra/overlay-server/.github/workflows/image.yaml deleted file mode 100644 index 99eb8ce59..000000000 --- a/infra/overlay-server/.github/workflows/image.yaml +++ /dev/null @@ -1,66 +0,0 @@ -name: Build and push OCI image to Docker Hub - -on: - push: - tags: - - 'v*' - -jobs: - check-current-branch: - runs-on: ubuntu-latest - outputs: - branch: ${{ steps.check_step.outputs.branch }} - steps: - - name: Checkout - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 - with: - fetch-depth: 0 - - - name: Get current branch - id: check_step - # 1. Get the list of branches ref where this tag exists - # 2. Remove 'origin/' from that result - # 3. Put that string in output - run: | - raw=$(git branch -r --contains ${{ github.ref }}) - branch="$(echo ${raw//origin\//} | tr -d '\n')" - echo "branch=$branch" >> $GITHUB_OUTPUT - echo "Branches where this tag exists : $branch." - - image: - runs-on: ubuntu-latest - needs: check-current-branch - if: contains(needs.check-current-branch.outputs.branch, 'main') - steps: - - name: Check out the repo - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Get build args - id: build_args - run: | - echo "APP_COMMIT=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - echo "APP_VERSION=$(git describe --tags --always --abbrev=0 --match='v[0-9]*.[0-9]*.[0-9]*' 2> /dev/null | sed 's/^.//')" >> "$GITHUB_OUTPUT" - - - name: Log in to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) - id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 - with: - images: bsvb/overlay-examples - - - name: Build and push image - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 - with: - context: . - file: ./Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - APP_COMMIT=${{ steps.build_args.outputs.APP_COMMIT }} - APP_VERSION=${{ steps.build_args.outputs.APP_VERSION }} diff --git a/infra/overlay-server/AGENTS.md b/infra/overlay-server/AGENTS.md index 5d5c33904..da1c81f82 100644 --- a/infra/overlay-server/AGENTS.md +++ b/infra/overlay-server/AGENTS.md @@ -1,198 +1,10 @@ -# CLAUDE.md +# ts-stack agent instructions -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This project follows the repository-wide [agent instructions](../../AGENTS.md) +and [contribution policy](../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -## Overview - -This repository provides ready-to-run configuration examples for standalone Overlay nodes built with `@bsv/overlay-express`. It implements various BSV blockchain overlay services (topic managers and lookup services) that enable distributed applications to organize and query blockchain data efficiently. - -## Development Commands - -```bash -# Install dependencies -npm install - -# Development with hot-reload (uses tsx) -npm run dev - -# Build TypeScript to dist/ -npm run build - -# Run production build -npm start - -# Docker Compose (full stack: app + MongoDB + MySQL) -docker compose up --build -``` - -## Environment Configuration - -All configuration is supplied via environment variables in `.env`. See `.env.example` for required variables: - -- **NODE_NAME**: One-word, lowercase overlay service node identifier -- **SERVER_PRIVATE_KEY**: 32-byte hex root private key for server wallet -- **HOSTING_URL**: Public URL where the node is reachable -- **ADMIN_TOKEN**: Token for admin API access -- **WALLET_STORAGE_URL**: BSV wallet storage endpoint (e.g., `https://store-us-1.bsvb.tech`) -- **NETWORK**: `main` or `test` (BSV blockchain network) -- **ARC_API_KEY**: ARC key for transaction broadcasting -- **MONGO_URL**: MongoDB connection string -- **KNEX_URL**: MySQL connection string for Knex -- **GASP_ENABLED**: `true` or `false` (Graph Aware Sync Protocol for overlay sync) - -## Architecture - -### Core Components - -The application bootstraps in [src/index.ts](src/index.ts): - -1. **OverlayExpress Server** - Main server instance configured with identity, hosting URL, and admin access -2. **WalletAdvertiser** - Manages BSV wallet and creates advertisements for the overlay -3. **Database Connections** - Knex (MySQL) and MongoDB connections -4. **Topic Managers** - Validate which transaction outputs are admissible to the overlay -5. **Lookup Services** - Enable querying and retrieving data from the overlay - -### Topic Managers & Lookup Services Pattern - -Each overlay service consists of a **Topic Manager** and **Lookup Service** pair: - -- **Topic Manager**: Implements `identifyAdmissibleOutputs(beef, previousCoins)` to validate transaction outputs. Each manager: - - Decodes outputs using PushDrop (BRC-48 standard) - - Validates data format and structure - - Verifies signatures and cryptographic proofs - - Returns `AdmittanceInstructions` with `outputsToAdmit` and `coinsToRetain` - -- **Lookup Service**: Implements storage and query methods: - - `outputAdmittedByTopic(payload)` - Store admitted outputs - - `outputSpent(payload)` - Handle spent outputs - - `lookup(question)` - Query stored data - - Factory pattern: exported as function taking `Db` (MongoDB) and returning service instance - -### Service Structure - -Services are organized in [src/services/](src/services/) with consistent patterns: - -``` -src/services/{service-name}/ - ├── {Service}TopicManager.ts # Validates outputs - ├── {Service}LookupServiceFactory.ts # Query interface - ├── {Service}StorageManager.ts # Data persistence - ├── {Service}Types.ts # TypeScript types - ├── {Service}TopicDocs.ts # Topic documentation - └── {Service}LookupDocs.md.ts # Lookup documentation -``` - -### Implemented Services - -The following overlay services are configured in [src/index.ts](src/index.ts): - -1. **ProtoMap** (`tm_protomap`, `ls_protomap`) - Protocol information registry -2. **CertMap** (`tm_certmap`, `ls_certmap`) - Certificate mapping -3. **BasketMap** (`tm_basketmap`, `ls_basketmap`) - Basket management -4. **UHRP** (`tm_uhrp`, `ls_uhrp`) - Universal Hash Resolution Protocol -5. **Identity** (`tm_identity`, `ls_identity`) - Identity services -6. **MessageBox** (`tm_messagebox`, `ls_messagebox`) - Message storage -7. **UMP** (`tm_users`, `ls_users`) - User management protocol -8. **HelloWorld** (`tm_helloworld`, `ls_helloworld`) - Simple messaging example -9. **SlackThreads** (`tm_slackthread`, `ls_slackthread`) - Slack-style threads -10. **DesktopIntegrity** (`tm_desktopintegrity`, `ls_desktopintegrity`) - Desktop app integrity verification -11. **Fractionalize** (`tm_fractionalize`, `ls_fractionalize`) - Fractional ownership -12. **Any** (`tm_anytx`, `ls_anytx`) - Generic transaction storage -13. **Apps** (`tm_apps`, `ls_apps`) - Application registry -14. **DID** (`tm_did`, `ls_did`) - Decentralized identifiers -15. **WalletConfig** (`tm_walletconfig`, `ls_walletconfig`) - Wallet configuration service discovery - -### Key Technical Patterns - -#### Topic Manager Implementation -```typescript -async identifyAdmissibleOutputs(beef: number[], previousCoins: number[]): Promise { - const outputsToAdmit: number[] = [] - const parsedTransaction = Transaction.fromBEEF(beef) - - for (const [i, output] of parsedTransaction.outputs.entries()) { - try { - const { fields, lockingPublicKey } = PushDrop.decode(output.lockingScript) - // Validate fields and verify signature - // If valid, push to outputsToAdmit - } catch (error) { - continue // Invalid outputs are silently skipped - } - } - - return { outputsToAdmit, coinsToRetain: [] } -} -``` - -#### Lookup Service Factory Pattern -```typescript -export default (db: Db): ServiceLookupService => { - return new ServiceLookupService(new ServiceStorageManager(db)) -} -``` - -#### Server Configuration Pattern -```typescript -// Topic managers use 'new' instantiation -server.configureTopicManager('tm_name', new ServiceTopicManager()) - -// Lookup services use factory functions -server.configureLookupServiceWithMongo('ls_name', ServiceLookupServiceFactory) -``` - -### BSV SDK Usage - -This codebase extensively uses `@bsv/sdk` for: -- **Transaction parsing**: `Transaction.fromBEEF(beef)` -- **PushDrop decoding**: `PushDrop.decode(lockingScript)` - BRC-48 standard -- **Key derivation**: `KeyDeriver` for deriving public keys from protocols -- **Signature verification**: `lockingPublicKey.verify()` and `ProtoWallet.verifySignature()` -- **Data encoding**: `Utils.toUTF8()`, `Utils.toHex()` - -### Database Usage - -- **MongoDB**: Primary storage for lookup service data (indexed queries) -- **MySQL/Knex**: Used by OverlayExpress engine for transaction tracking -- Storage managers handle MongoDB collections with CRUD operations - -## Deployment - -### Docker -- Multi-stage [Dockerfile](Dockerfile) builds TypeScript and runs production server -- [docker-compose.yml](docker-compose.yml) includes app, MongoDB, MySQL, and janitor services -- Janitor service runs cron job to clean stale tokens via `/admin/janitor` endpoint - -### Kubernetes -Deployment files in [deploy/](deploy/): -- `app-deployment.yaml` - Main application deployment -- `app-service.yaml` - Service configuration -- `mongodb-deployment.yaml` + `mongodb-service.yaml` - MongoDB setup -- `mysql-deployment.yaml` + `mysql-service.yaml` - MySQL setup -- `janitor-cronjob.yaml` - Scheduled cleanup job -- Persistent volumes for database storage - -## Adding New Services - -To add a new overlay service: - -1. Create service directory in `src/services/{service-name}/` -2. Implement `TopicManager` with `identifyAdmissibleOutputs()` method -3. Implement `LookupService` with `outputAdmittedByTopic()`, `outputSpent()`, and `lookup()` methods -4. Create `StorageManager` for MongoDB persistence -5. Define TypeScript types in `types.ts` -6. Add documentation in `*Docs.ts` files -7. Register in `src/index.ts`: - ```typescript - server.configureTopicManager('tm_{name}', new ServiceTopicManager()) - server.configureLookupServiceWithMongo('ls_{name}', ServiceLookupServiceFactory) - ``` - -## Important Notes - -- Topic manager IDs start with `tm_`, lookup service IDs with `ls_` -- Topic managers validate cryptographic proofs and signatures -- Invalid outputs should be silently skipped in topic managers (don't throw errors) -- Lookup services use factory pattern returning instances from MongoDB connection -- GASP sync enables multi-node overlay synchronization (disable for simple local deployments) -- Server listens on port 8080 by default -- Admin API requires `ADMIN_TOKEN` in Authorization header +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/infra/uhrp-server-basic/AGENTS.md b/infra/uhrp-server-basic/AGENTS.md index 952fcaec3..da1c81f82 100644 --- a/infra/uhrp-server-basic/AGENTS.md +++ b/infra/uhrp-server-basic/AGENTS.md @@ -1,69 +1,10 @@ -# CLAUDE.md — UHRP Lite (Basic File Storage) +# ts-stack agent instructions -## Purpose -A simple, file-system based UHRP (Universal Host Reference Protocol) host server. Stores files locally on disk, provides HTTP GET/PUT/POST endpoints for UHRP data retrieval and storage, and bills users per GB/month. Designed as a lightweight alternative to cloud-based storage for developers wanting to run UHRP locally. +This project follows the repository-wide [agent instructions](../../AGENTS.md) +and [contribution policy](../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -## Service surface -- **PUT /put/{hash}** – Upload file to local storage (authenticated, priced) -- **GET /{hash}** – Retrieve file from local storage (public) -- **POST /lookup** – UHRP lookup queries to find and metadata for stored files (public) -- **GET /info** – Server info and pricing details (public) -- **Health/readiness** – HTTP 200 on GET / (no explicit health endpoint) - -## Real deployment -- **No Dockerfile** – Lightweight Node.js server only, uses ts-node directly -- **No docker-compose.yml** – Filesystem-based, no database dependencies -- **No nginx.conf** – Direct Express server on configured port -- **File storage** – Local filesystem (`/data` or `./public` by default, configurable via env) -- **No migrations** – Stateless server; files stored directly on disk with JSON metadata - -## Configuration -Environment variables (from `.env.example`): -- **PRICE_PER_GB_MO** – Monthly storage price per GB (e.g., `0.03`) -- **HOSTING_DOMAIN** – Public domain for server advertisement (e.g., `localhost:8080` or `https://uhrp.example.com`) -- **BSV_NETWORK** – Target blockchain network (e.g., `mainnet` or `testnet`) -- **WALLET_STORAGE_URL** – Wallet storage endpoint for key derivation (e.g., `https://storage.babbage.systems`) -- **SERVER_PRIVATE_KEY** – 256-bit hex private key for server identity (required) -- **HTTP_PORT** – Express server port (default: 8080) -- **NODE_ENV** – `development` or `production` - -## Dependencies -- **Database** – None; filesystem-based storage -- **@bsv packages** - - `@bsv/sdk` – Cryptography, key operations - - `@bsv/auth-express-middleware` – Request/response authentication for PUT - - `@bsv/payment-express-middleware` – Price calculation and optional payment verification - - `@bsv/wallet-toolbox-client` – Wallet client interface -- **External services** - - Wallet Storage (via `WALLET_STORAGE_URL`) – Key derivation, payment validation - - ARC / transaction broadcaster – Optional for payment transactions -- **Key packages** – Express, body-parser, dotenv, axios - -## Operational concerns -- **Local dev** – `npm run dev` with nodemon hot-reload, stores files in `./public` or configured data dir -- **Production** – `npm run build && npm start` compiles TypeScript to `out/`, runs `node out/src/index.js` -- **File storage** – No cleanup mechanism; files persist until manually deleted. Monitor disk usage in production -- **Pricing** – PRICE_PER_GB_MO is advisory; actual payment enforcement depends on payment middleware configuration -- **Authentication** – PUT requires signed auth headers (BRC-103); GET/POST are public -- **Scaling** – Single instance; no built-in replication or load balancing -- **MIME types** – Auto-detected based on file extension via custom middleware - -## Spec conformance -- **UHRP** – Implements basic UHRP host protocol for file storage and retrieval -- **BRC-103** – Mutual authentication on authenticated endpoints (PUT) -- **BRC-100** – Optional payment verification (via payment middleware) - -## Integration points -- **UHRP clients** – Any UHRP-aware client can upload/retrieve files using SERVER_PRIVATE_KEY and HOSTING_DOMAIN -- **Wallet Storage** – Derives keys from SERVER_PRIVATE_KEY, validates payments -- **Overlay nodes** – Can advertise UHRP hosting capability via overlay - -## File map -- **src/** - - `index.ts` – Entry point: Express app setup, routes, auth middleware, payment integration - - `routes/` – HTTP handlers: PUT, GET, lookup, info - - `utils/` – Helpers: file pricing, wallet singleton, metadata retrieval, MIME type detection - - `public/` – Static assets and uploaded file storage (configurable) -- **package.json** – Scripts: `dev`, `build`, `start` -- **.env.example** – Template with all required/optional variables -- **tsconfig.json** – TypeScript configuration +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/infra/uhrp-server-cloud-bucket/.github/dependabot.yml b/infra/uhrp-server-cloud-bucket/.github/dependabot.yml deleted file mode 100644 index af97a4d95..000000000 --- a/infra/uhrp-server-cloud-bucket/.github/dependabot.yml +++ /dev/null @@ -1,7 +0,0 @@ -version: 2 - -updates: - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" diff --git a/infra/uhrp-server-cloud-bucket/.github/workflows/build.yaml b/infra/uhrp-server-cloud-bucket/.github/workflows/build.yaml deleted file mode 100644 index 892bab7a4..000000000 --- a/infra/uhrp-server-cloud-bucket/.github/workflows/build.yaml +++ /dev/null @@ -1,66 +0,0 @@ -name: Build and push OCI image to Docker Hub - -on: - push: - tags: - - 'v*' - -jobs: - check-current-branch: - runs-on: ubuntu-latest - outputs: - branch: ${{ steps.check_step.outputs.branch }} - steps: - - name: Checkout - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 - with: - fetch-depth: 0 - - - name: Get current branch - id: check_step - # 1. Get the list of branches ref where this tag exists - # 2. Remove 'origin/' from that result - # 3. Put that string in output - run: | - raw=$(git branch -r --contains ${{ github.ref }}) - branch="$(echo ${raw//origin\//} | tr -d '\n')" - echo "branch=$branch" >> $GITHUB_OUTPUT - echo "Branches where this tag exists : $branch." - - image: - runs-on: ubuntu-latest - needs: check-current-branch - if: contains(needs.check-current-branch.outputs.branch, 'master') - steps: - - name: Check out the repo - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Get build args - id: build_args - run: | - echo "APP_COMMIT=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - echo "APP_VERSION=$(git describe --tags --always --abbrev=0 --match='v[0-9]*.[0-9]*.[0-9]*' 2> /dev/null | sed 's/^.//')" >> "$GITHUB_OUTPUT" - - - name: Log in to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) - id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 - with: - images: bsvb/uhrp-storage-server - - - name: Build and push image - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 - with: - context: . - file: ./Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - APP_COMMIT=${{ steps.build_args.outputs.APP_COMMIT }} - APP_VERSION=${{ steps.build_args.outputs.APP_VERSION }} diff --git a/infra/uhrp-server-cloud-bucket/.github/workflows/deploy.yaml b/infra/uhrp-server-cloud-bucket/.github/workflows/deploy.yaml deleted file mode 100644 index 2cfb2e17c..000000000 --- a/infra/uhrp-server-cloud-bucket/.github/workflows/deploy.yaml +++ /dev/null @@ -1,140 +0,0 @@ -name: Deployment - -on: - push: - branches: - - master - - production - workflow_dispatch: - -env: - CURRENT_BRANCH: ${{ github.ref_name == 'production' && 'production' || 'master' }} - -jobs: - build: - name: Deploy - runs-on: ubuntu-latest - environment: ${{ github.ref_name == 'production' && 'production' || 'staging' }} - env: - GCR_HOST: ${{ secrets.GCR_HOST }} - GOOGLE_PROJECT_ID: ${{ secrets.GOOGLE_PROJECT_ID }} - GCR_IMAGE_NAME: ${{ secrets.GCR_IMAGE_NAME }} - GCP_BUCKET_NAME: ${{ secrets.GCP_BUCKET_NAME }} - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} - DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} - steps: - - name: Debug secrets presence (production) - if: ${{ github.ref_name == 'production' }} - run: | - echo "ENV=production" - echo "ADMIN_TOKEN set? ${{ secrets.ADMIN_TOKEN != '' }}" - echo "BSV_NETWORK set? ${{ secrets.BSV_NETWORK != '' }}" - echo "GCP_BUCKET_NAME set? ${{ secrets.GCP_BUCKET_NAME != '' }}" - echo "GCR_HOST set? ${{ secrets.GCR_HOST != '' }}" - echo "GOOGLE_PROJECT_ID set? ${{ secrets.GOOGLE_PROJECT_ID != '' }}" - echo "GCR_IMAGE_NAME set? ${{ secrets.GCR_IMAGE_NAME != '' }}" - echo "NODE_ENV set? ${{ secrets.NODE_ENV != '' }}" - echo "HOSTING_DOMAIN set? ${{ secrets.HOSTING_DOMAIN != '' }}" - echo "PRICE_PER_GB_MO set? ${{ secrets.PRICE_PER_GB_MO != '' }}" - echo "GCP_STORAGE_CREDS set? ${{ secrets.GCP_STORAGE_CREDS != '' }}" - echo "GCR_PUSH_KEY set? ${{ secrets.GCR_PUSH_KEY != '' }}" - echo "SERVER_PRIVATE_KEY set? ${{ secrets.SERVER_PRIVATE_KEY != '' }}" - echo "MIN_HOSTING_MINUTES set? ${{ secrets.MIN_HOSTING_MINUTES != '' }}" - echo "WALLET_STORAGE_URL set? ${{ secrets.WALLET_STORAGE_URL != '' }}" - echo "HTTP_PORT set? ${{ secrets.HTTP_PORT != '' }}" - - - name: Debug secrets presence (staging) - if: ${{ github.ref_name != 'production' }} - run: | - echo "ENV=staging" - echo "ADMIN_TOKEN set? ${{ secrets.ADMIN_TOKEN != '' }}" - echo "BSV_NETWORK set? ${{ secrets.BSV_NETWORK != '' }}" - echo "GCP_BUCKET_NAME set? ${{ secrets.GCP_BUCKET_NAME != '' }}" - echo "GCR_HOST set? ${{ secrets.GCR_HOST != '' }}" - echo "GOOGLE_PROJECT_ID set? ${{ secrets.GOOGLE_PROJECT_ID != '' }}" - echo "GCR_IMAGE_NAME set? ${{ secrets.GCR_IMAGE_NAME != '' }}" - echo "NODE_ENV set? ${{ secrets.NODE_ENV != '' }}" - echo "HOSTING_DOMAIN set? ${{ secrets.HOSTING_DOMAIN != '' }}" - echo "PRICE_PER_GB_MO set? ${{ secrets.PRICE_PER_GB_MO != '' }}" - echo "GCP_STORAGE_CREDS set? ${{ secrets.GCP_STORAGE_CREDS != '' }}" - echo "GCR_PUSH_KEY set? ${{ secrets.GCR_PUSH_KEY != '' }}" - echo "SERVER_PRIVATE_KEY set? ${{ secrets.SERVER_PRIVATE_KEY != '' }}" - echo "MIN_HOSTING_MINUTES set? ${{ secrets.MIN_HOSTING_MINUTES != '' }}" - echo "WALLET_STORAGE_URL set? ${{ secrets.WALLET_STORAGE_URL != '' }}" - echo "HTTP_PORT set? ${{ secrets.HTTP_PORT != '' }}" - - - name: Login to Docker Hub - if: ${{ env.DOCKERHUB_USERNAME != '' && env.DOCKERHUB_PASSWORD != '' }} - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - username: ${{ env.DOCKERHUB_USERNAME }} - password: ${{ env.DOCKERHUB_PASSWORD }} - - - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2 - - - name: Authenticate to Google Cloud - uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2 - with: - credentials_json: ${{ secrets.GCR_PUSH_KEY }} - - - name: Setup gcloud SDK - uses: google-github-actions/setup-gcloud@e427ad8a34f8676edf47cf7d7925499adf3eb74f # v2 - - - name: Configure Docker Auth for GCR - run: gcloud auth configure-docker ${{ env.GCR_HOST }} --quiet - - - name: Build and Push Docker Image to GCR - run: | - IMAGE_BASE="${{ env.GCR_HOST }}/${{ env.GOOGLE_PROJECT_ID }}/${{ env.GCR_IMAGE_NAME }}" - IMAGE_TAG="${{ env.CURRENT_BRANCH }}-${{ github.sha }}" - docker build \ - -t "${IMAGE_BASE}:latest" \ - -t "${IMAGE_BASE}:${IMAGE_TAG}" \ - . - docker push "${IMAGE_BASE}:latest" - docker push "${IMAGE_BASE}:${IMAGE_TAG}" - - - name: "Create service description file" - run: "./scripts/mkenv.sh service.${{ env.CURRENT_BRANCH }}.yaml ./notifier/${{ env.CURRENT_BRANCH == 'production' && 'prod' || 'staging' }}.functions.env.yaml" - env: - IMAGE: "${{ env.GCR_HOST }}/${{ env.GOOGLE_PROJECT_ID }}/${{ env.GCR_IMAGE_NAME }}:${{ env.CURRENT_BRANCH }}-${{ github.sha }}" - SERVICE: ${{ env.CURRENT_BRANCH == 'production' && 'prod-uhrp' || 'staging-uhrp' }} - NODE_ENV: ${{ secrets.NODE_ENV }} - HOSTING_DOMAIN: ${{ secrets.HOSTING_DOMAIN }} - PRICE_PER_GB_MO: ${{ secrets.PRICE_PER_GB_MO }} - GCP_STORAGE_CREDS: ${{ secrets.GCP_STORAGE_CREDS }} - GCP_BUCKET_NAME: ${{ env.GCP_BUCKET_NAME }} - GCP_PROJECT_ID: ${{ env.GOOGLE_PROJECT_ID }} - SERVER_PRIVATE_KEY: ${{ secrets.SERVER_PRIVATE_KEY }} - MIN_HOSTING_MINUTES: ${{ secrets.MIN_HOSTING_MINUTES }} - ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }} - WALLET_STORAGE_URL: ${{ secrets.WALLET_STORAGE_URL }} - BSV_NETWORK: ${{ secrets.BSV_NETWORK }} - HTTP_PORT: ${{ secrets.HTTP_PORT }} - - - name: "Authenticate with GCP" - id: auth - uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2 - with: - credentials_json: ${{ secrets.GCP_STORAGE_CREDS }} - - name: "Determine bucket region from Cloud Storage" - id: bucket - run: | - REGION=$(gcloud storage buckets describe gs://$GCP_BUCKET_NAME --format='value(location)' | tr '[:upper:]' '[:lower:]') - echo "Bucket $GCP_BUCKET_NAME is in region: $REGION" - echo "region=$REGION" >> "$GITHUB_OUTPUT" - - uses: google-github-actions/deploy-cloudrun@e62f655d5754bec48078a72edc015367b01ee97b # v1 - with: - metadata: "service.${{ env.CURRENT_BRANCH }}.yaml" - region: ${{ steps.bucket.outputs.region }} - - name: "Deploy the notifier function" - run: | - cd notifier - ./deploy${{ env.CURRENT_BRANCH == 'production' && 'Prod' || 'Staging' }}.sh "$GCP_BUCKET_NAME" - - name: Allow public (allUsers) to invoke the service - run: | - gcloud run services add-iam-policy-binding ${{ env.CURRENT_BRANCH == 'production' && 'prod-uhrp' || 'staging-uhrp' }} \ - --region ${{ steps.bucket.outputs.region }} \ - --platform managed \ - --member="allUsers" \ - --role="roles/run.invoker" diff --git a/infra/uhrp-server-cloud-bucket/.github/workflows/setup.yaml b/infra/uhrp-server-cloud-bucket/.github/workflows/setup.yaml deleted file mode 100644 index 3249de6dd..000000000 --- a/infra/uhrp-server-cloud-bucket/.github/workflows/setup.yaml +++ /dev/null @@ -1,248 +0,0 @@ -name: Setup GCP (Bootstrap) - -on: - workflow_dispatch: - inputs: - env: - description: "Target environment (staging or production)" - type: choice - required: true - options: [staging, production] - default: staging - region: - description: "GCP region (e.g., us-west1)" - required: true - default: us-west1 - -env: - ENV_NAME: ${{ github.event.inputs.env }} # staging | production - REGION: ${{ github.event.inputs.region }} # e.g., us-west1 - -jobs: - setup: - runs-on: ubuntu-latest - permissions: - actions: write - contents: write - id-token: write - environment: ${{ github.event.inputs.env }} - env: - GOOGLE_PROJECT_ID: ${{ secrets.GOOGLE_PROJECT_ID }} - GCR_HOST: ${{ secrets.GCR_HOST }} - GCR_IMAGE_NAME: ${{ secrets.GCR_IMAGE_NAME }} - GCP_BUCKET_NAME: ${{ secrets.GCP_BUCKET_NAME }} - - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - # ----- load env-scoped config secrets (unchanged from your deploy) ----- - - name: Load environment config - run: | - echo "PROJECT_ID=${{ env.GOOGLE_PROJECT_ID }}" >> $GITHUB_ENV - echo "BUCKET_NAME=${{ env.GCP_BUCKET_NAME }}" >> $GITHUB_ENV - echo "GCR_HOST=${{ env.GCR_HOST }}" >> $GITHUB_ENV - echo "IMAGE_NAME=${{ env.GCR_IMAGE_NAME }}" >> $GITHUB_ENV - - # ----- authenticate with bootstrap owner key ----- - - name: Write bootstrap key - env: - GCP_BOOTSTRAP_KEY: ${{ secrets.GCP_BOOTSTRAP_KEY }} - run: | - printf '%s' "$GCP_BOOTSTRAP_KEY" > "$RUNNER_TEMP/bootstrap.json" - - - name: Auth to GCP (bootstrap) - uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2 - with: - credentials_json: ${{ secrets.GCP_BOOTSTRAP_KEY }} - - name: Fill PROJECT_ID from auth context (fallback) - run: | - if [ -z "$PROJECT_ID" ]; then - if [ -n "$CLOUDSDK_CORE_PROJECT" ]; then - echo "PROJECT_ID=$CLOUDSDK_CORE_PROJECT" >> $GITHUB_ENV - elif [ -n "$GOOGLE_CLOUD_PROJECT" ]; then - echo "PROJECT_ID=$GOOGLE_CLOUD_PROJECT" >> $GITHUB_ENV - elif [ -n "$GCP_PROJECT" ]; then - echo "PROJECT_ID=$GCP_PROJECT" >> $GITHUB_ENV - fi - fi - - - name: Setup gcloud SDK - uses: google-github-actions/setup-gcloud@e427ad8a34f8676edf47cf7d7925499adf3eb74f # v2 - with: - project_id: ${{ env.GOOGLE_PROJECT_ID }} - - - name: Select token for GitHub API - env: - GH_ADMIN_TOKEN: ${{ secrets.GH_ADMIN_TOKEN }} - GH_TOKEN_FALLBACK: ${{ github.token }} - run: | - if [ -n "$GH_ADMIN_TOKEN" ]; then - echo "GH_TOKEN=$GH_ADMIN_TOKEN" >> "$GITHUB_ENV" - else - echo "GH_TOKEN=$GH_TOKEN_FALLBACK" >> "$GITHUB_ENV" - fi - - - name: Ensure GitHub CLI is installed - run: | - if ! command -v gh >/dev/null 2>&1; then - sudo apt-get update -y - sudo apt-get install -y gh - fi - gh --version - - - name: Ensure GitHub environments exist - env: - GH_TOKEN: ${{ env.GH_TOKEN }} - run: | - for ENV in staging production; do - gh api -X PUT "repos/${{ github.repository }}/environments/$ENV" >/dev/null || true - done - - - name: Set gcloud project - run: | - gcloud config set project "$PROJECT_ID" - gcloud config set run/region "${{ env.REGION }}" - - # ----- enable all required APIs (idempotent) ----- - - name: Enable required APIs - run: | - gcloud services enable \ - run.googleapis.com \ - cloudfunctions.googleapis.com \ - logging.googleapis.com \ - pubsub.googleapis.com \ - eventarc.googleapis.com \ - artifactregistry.googleapis.com \ - cloudbuild.googleapis.com \ - serviceusage.googleapis.com \ - cloudresourcemanager.googleapis.com \ - iam.googleapis.com \ - compute.googleapis.com - - # ----- create bucket if missing + optional CORS ----- - - name: Create or update GCS bucket (Autoclass ON, Soft delete OFF) - run: | - set -e - if gcloud storage buckets describe "gs://$BUCKET_NAME" >/dev/null 2>&1; then - echo "Bucket exists; updating settings…" - gcloud storage buckets update "gs://$BUCKET_NAME" --enable-autoclass - gcloud storage buckets update "gs://$BUCKET_NAME" --clear-soft-delete - gcloud storage buckets update "gs://$BUCKET_NAME" --no-public-access-prevention - else - echo "Creating bucket gs://$BUCKET_NAME in $PROJECT_ID ($REGION)…" - gcloud storage buckets create "gs://$BUCKET_NAME" \ - --project="$PROJECT_ID" \ - --location="$REGION" \ - --uniform-bucket-level-access \ - --enable-autoclass \ - --no-public-access-prevention - gcloud storage buckets update "gs://$BUCKET_NAME" --clear-soft-delete - gcloud storage buckets add-iam-policy-binding "gs://${BUCKET_NAME}" \ - --member="allUsers" \ - --role="roles/storage.objectViewer" - fi - - # Apply CORS only if the bucket is accessible in this project - if [ -f bucket-cors-config.json ]; then - if gcloud storage buckets describe "gs://$BUCKET_NAME" >/dev/null 2>&1; then - gsutil cors set bucket-cors-config.json "gs://$BUCKET_NAME" - else - echo "Skipping CORS: bucket gs://$BUCKET_NAME is not accessible in project $PROJECT_ID." - fi - fi - - # ----- eventarc identity + IAM plumbing ----- - - name: Ensure Eventarc service identity exists - run: | - gcloud beta services identity create --service=eventarc.googleapis.com || true - PROJECT_NUM=$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)') - echo "PROJECT_NUM=$PROJECT_NUM" >> $GITHUB_ENV - echo "EVENTARC_SA=service-${PROJECT_NUM}@gcp-sa-eventarc.iam.gserviceaccount.com" >> $GITHUB_ENV - echo "PROJECT_SA=service-${PROJECT_NUM}@gs-project-accounts.iam.gserviceaccount.com" >> $GITHUB_ENV - - - name: Grant Eventarc SA read access to bucket - if: env.BUCKET_NAME != '' - run: | - gcloud storage buckets add-iam-policy-binding "gs://$BUCKET_NAME" \ - --member="serviceAccount:${EVENTARC_SA}" \ - --role="roles/storage.legacyBucketReader" || true - - - name: Allow GS Project SA to publish to Pub/Sub - run: | - gcloud projects add-iam-policy-binding "$PROJECT_ID" \ - --member="serviceAccount:${PROJECT_SA}" \ - --role="roles/pubsub.publisher" || true - - # ----- create deployer SA + find default compute SA ----- - - name: Create uhrp-github-deployer service account - run: | - SA_NAME="uhrp-github-deployer" - SA_EMAIL="${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" - if ! gcloud iam service-accounts describe "$SA_EMAIL" >/dev/null 2>&1; then - gcloud iam service-accounts create "$SA_NAME" --display-name="UHRP GitHub Deployer" - fi - echo "DEPLOYER_SA=$SA_EMAIL" >> $GITHUB_ENV - - - name: Discover Default Compute Engine service account - run: | - COMPUTE_SA="$(gcloud iam service-accounts list --format='value(email)' \ - --filter='email~compute@developer.gserviceaccount.com')" - if [ -z "$COMPUTE_SA" ]; then - echo "Default Compute Engine SA not found." >&2 - exit 1 - fi - echo "COMPUTE_SA=$COMPUTE_SA" >> $GITHUB_ENV - - # ----- assign roles (idempotent) ----- - - name: Assign roles to Deploy SA - run: | - for ROLE in \ - roles/run.admin \ - roles/cloudfunctions.admin \ - roles/cloudbuild.builds.editor \ - roles/artifactregistry.admin \ - roles/storage.objectAdmin \ - roles/iam.serviceAccountUser - do - gcloud projects add-iam-policy-binding "$PROJECT_ID" \ - --member="serviceAccount:${DEPLOYER_SA}" \ - --role="$ROLE" || true - done - - - name: Assign roles to Default Compute SA - run: | - for ROLE in \ - roles/artifactregistry.admin \ - roles/storage.bucketViewer \ - roles/cloudfunctions.admin \ - roles/run.admin \ - roles/eventarc.admin \ - roles/logging.logWriter \ - roles/iam.serviceAccountAdmin \ - roles/iam.serviceAccountUser \ - roles/storage.objectAdmin - do - gcloud projects add-iam-policy-binding "$PROJECT_ID" \ - --member="serviceAccount:${COMPUTE_SA}" \ - --role="$ROLE" || true - done - - # ----- create Artifact Registry repo from repo/image ----- - - name: Create Artifact Registry repository - if: env.IMAGE_NAME != '' - run: | - REPO_NAME="$(echo "$IMAGE_NAME" | cut -d'/' -f1)" # repo/image → repo - if ! gcloud artifacts repositories describe "$REPO_NAME" --location="${{ env.REGION }}" >/dev/null 2>&1; then - gcloud artifacts repositories create "$REPO_NAME" \ - --repository-format=docker \ - --location="${{ env.REGION }}" - else - echo "Repo '$REPO_NAME' already exists in ${{ env.REGION }}." - fi - - - name: Summary - run: | - echo "Bootstrap complete for project: $PROJECT_ID" - echo "Deployer SA: $DEPLOYER_SA" - echo "Default Compute SA: $COMPUTE_SA" diff --git a/infra/uhrp-server-cloud-bucket/AGENTS.md b/infra/uhrp-server-cloud-bucket/AGENTS.md index b16017e3e..da1c81f82 100644 --- a/infra/uhrp-server-cloud-bucket/AGENTS.md +++ b/infra/uhrp-server-cloud-bucket/AGENTS.md @@ -1,94 +1,10 @@ -# CLAUDE.md — UHRP Storage Server (Cloud Bucket) +# ts-stack agent instructions -## Purpose -A production-grade UHRP host server backed by Google Cloud Storage (or S3-compatible buckets). Stores large files in cloud buckets, provides HTTP endpoints for UHRP data retrieval and storage with billing/micropayments, and includes advertising infrastructure. Designed for high-volume deployments on Google Cloud Run with Cloud SQL. +This project follows the repository-wide [agent instructions](../../AGENTS.md) +and [contribution policy](../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -## Service surface -- **PUT /put/{hash}** – Upload file to cloud bucket (authenticated, priced, size-limited) -- **GET /{hash}** – Retrieve file from cloud bucket (public) -- **POST /lookup** – UHRP metadata lookup queries (public) -- **GET /info** – Server info, pricing, and status (public) -- **Advertising** – Background worker broadcasts UHRP host advertisement to overlay network -- **Health/readiness** – Implicit health via /info endpoint - -## Real deployment -- **Dockerfile** – Multi-stage: digest-pinned Node 24 alpine builder → production runtime with ts-node -- **No docker-compose.yml** – Designed for Google Cloud Run + Cloud SQL, not Docker Compose -- **No nginx.conf** – Cloud Run handles HTTP/2, load balancing, and SSL -- **Cloud Storage** – Google Cloud Storage bucket via `@google-cloud/storage` SDK -- **Optional backup storage** – Supports multiple storage providers (local fallback, MySQL blob storage) -- **No migrations** – Stateless; cloud bucket is the source of truth - -## Configuration -Environment variables: -- **HTTP_PORT** – Express server port (default: 8080, typically 8080 for Cloud Run) -- **NODE_ENV** – `development`, `staging`, or `production` -- **SERVER_PRIVATE_KEY** – 256-bit hex private key for server identity (required) -- **HOSTING_DOMAIN** – Public HTTPS domain for advertising (e.g., `https://uhrp-storage.example.com`) -- **BSV_NETWORK** – Target blockchain network (`main`, `test`, or `regtest`) -- **WALLET_STORAGE_URL** – Wallet storage endpoint (e.g., `https://storage.babbage.systems`) -- **PRICE_PER_GB_MO** – Monthly storage price per GB for billing -- **ENABLE_PAYMENT_MIDDLEWARE** – Set to `'true'` to require payment for uploads -- **GOOGLE_CLOUD_PROJECT** – GCP project ID (auto-detected from service account if available) -- **GOOGLE_CLOUD_BUCKET** – Cloud Storage bucket name (e.g., `uhrp-storage-prod`) -- **GOOGLE_APPLICATION_CREDENTIALS** – Path to service account JSON key (for local/Cloud Run auth) -- **ARC_API_KEY** – ARC API key for transaction broadcasting (advertising) -- **ADVERTISE_INTERVAL_MS** – Interval for re-advertising to overlay (default: 3600000ms = 1 hour) -- **BUGSNAG_API_KEY** – Bugsnag error reporting API key (optional) - -## Dependencies -- **Database** – Optional MySQL via Knex (for backup storage or metadata tracking); not required if using cloud-only -- **@bsv packages** - - `@bsv/sdk` – Cryptography, key operations, transaction signing - - `@bsv/auth-express-middleware` – Request/response authentication for PUT - - `@bsv/payment-express-middleware` – Price calculation and payment verification - - `@bsv/wallet-toolbox` – Wallet operations, key derivation - - `@bsv/wallet-toolbox-client` – Wallet client interface -- **Cloud infrastructure** - - Google Cloud Storage – File storage via `@google-cloud/storage` - - Google Cloud Logging – Via Bunyan integration (optional) - - Google Cloud IAM – Service account authentication -- **External services** - - Wallet Storage (via `WALLET_STORAGE_URL`) – Key derivation, payment validation - - ARC – Transaction broadcaster for advertising transactions - - Overlay network – Advertises UHRP host capability -- **Error tracking** – Bugsnag via `@bugsnag/js` and `@bugsnag/plugin-express` (optional) -- **Key packages** – Express, body-parser, dotenv, axios, semver - -## Operational concerns -- **Deployment** – Designed for **Google Cloud Run** with Cloud SQL backing; follows GCP's 12-factor patterns -- **Local dev** – `npm run dev` with nodemon, requires GCP service account credentials or emulator -- **Production** – `npm run build && npm start` or via Cloud Run container with gcloud deploy -- **Scaling** – Cloud Run auto-scales; stateless design (files in cloud bucket, session data optional) -- **Storage** – Google Cloud Storage; cost based on actual usage + configured PRICE_PER_GB_MO -- **Payment** – Optional micropayment enforcement via payment middleware; transactions broadcast via ARC -- **Secrets** – Use Google Secret Manager or Cloud Run environment variables for sensitive values -- **Monitoring** – Bugsnag for error tracking, Cloud Logging for audit/access logs -- **Backups** – Cloud Storage handles durability; consider multi-region bucket configuration - -## Spec conformance -- **UHRP** – Implements UHRP host protocol for file storage, retrieval, and metadata -- **BRC-103** – Mutual authentication on PUT, optional on GET/POST -- **BRC-100** – Payment verification for uploads (optional) -- **Google Cloud** – Follows Cloud Run best practices (health checks, graceful shutdown, 12-factor) - -## Integration points -- **UHRP clients** – Any UHRP-aware client uploads/retrieves files using SERVER_PRIVATE_KEY and HOSTING_DOMAIN -- **Wallet Storage** – Derives keys, validates payments, manages user accounts -- **Overlay network** – Background worker advertises UHRP host via SHIP overlay protocol -- **ARC** – Broadcasts advertising and payment transactions to BSV network -- **Google Cloud ecosystem** – Secret Manager, Cloud SQL, Cloud Logging, Cloud Monitoring - -## File map -- **src/** - - `index.ts` – Entry point: Express app, cloud storage init, advertising loop, graceful shutdown - - `routes/` – HTTP handlers: PUT (upload), GET (download), POST (lookup), GET /info - - `utils/` – Helpers: file pricing, wallet singleton, metadata, bucket operations, error handling - - `config/` – Storage provider abstraction (Google Cloud, MySQL fallback, local filesystem) -- **scripts/** - - `sync-secrets.ts` – Cloud Secret Manager sync to .env (staging/prod) - - `verify-config.ts` – Pre-deployment config validation -- **Dockerfile** – Multi-stage digest-pinned Node 24 alpine builder → production -- **package.json** – Scripts: `dev`, `build`, `start`, `secrets:staging`, `secrets:prod` -- **.env.example** – Template with GCP, wallet, and storage config -- **README.md** – Deployment guide for Google Cloud Run setup +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/infra/wab/.github/workflows/deploy.yaml.example b/infra/wab/.github/workflows/deploy.yaml.example deleted file mode 100644 index 409235815..000000000 --- a/infra/wab/.github/workflows/deploy.yaml.example +++ /dev/null @@ -1,71 +0,0 @@ -name: Deployment -on: - push: - branches: - - master - - production - -env: - CURRENT_BRANCH: ${{ github.ref_name == 'production' && 'production' || 'master' }} - CWI_NPM_TOKEN: ${{ secrets.CWI_NPM_TOKEN }} - GCR_HOST: us.gcr.io - GOOGLE_PROJECT_ID: babbage-private - # Updated image name to "web-server" - GCR_IMAGE_NAME: web-server - -jobs: - build: - name: Deploy - runs-on: ubuntu-latest - steps: - - name: Check out code - uses: actions/checkout@v2 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: 22 - - - name: Install Dependencies - run: npm ci - - - name: Push Docker Image to GCR - uses: RafikFarhad/push-to-gcr-github-action@v4.1 - with: - gcloud_service_key: ${{ secrets.DOCKER_REGISTRY_PUSH_KEY }} - registry: ${{ env.GCR_HOST }} - project_id: ${{ env.GOOGLE_PROJECT_ID }} - image_name: ${{ env.GCR_IMAGE_NAME }} - image_tag: latest,${{ env.CURRENT_BRANCH }}-${{ github.sha }} - - - name: Create Service Description File - run: ./scripts/mkenv.sh service.${{ env.CURRENT_BRANCH }}.yaml - env: - IMAGE: "${{ env.GCR_HOST }}/${{ env.GOOGLE_PROJECT_ID }}/${{ env.GCR_IMAGE_NAME }}:${{ env.CURRENT_BRANCH }}-${{ github.sha }}" - SERVICE: ${{ env.CURRENT_BRANCH == 'production' && 'prod-wab-server' || 'staging-wab-server' }} - # New environment variables for Cloud SQL and Twilio: - DB_HOST: ${{ env.CURRENT_BRANCH == 'production' && secrets.PROD_DB_HOST || secrets.STAGING_DB_HOST }} - DB_USER: ${{ env.CURRENT_BRANCH == 'production' && secrets.PROD_DB_USER || secrets.STAGING_DB_USER }} - DB_PASS: ${{ env.CURRENT_BRANCH == 'production' && secrets.PROD_DB_PASS || secrets.STAGING_DB_PASS }} - DB_NAME: ${{ env.CURRENT_BRANCH == 'production' && secrets.PROD_DB_NAME || secrets.STAGING_DB_NAME }} - TWILIO_ACCOUNT_SID: ${{ env.CURRENT_BRANCH == 'production' && secrets.TWILIO_ACCOUNT_SID_PROD || secrets.TWILIO_ACCOUNT_SID_STAGING }} - TWILIO_AUTH_TOKEN: ${{ env.CURRENT_BRANCH == 'production' && secrets.TWILIO_AUTH_TOKEN_PROD || secrets.TWILIO_AUTH_TOKEN_STAGING }} - TWILIO_VERIFY_SERVICE_SID: ${{ env.CURRENT_BRANCH == 'production' && secrets.TWILIO_VERIFY_SERVICE_SID_PROD || secrets.TWILIO_VERIFY_SERVICE_SID_STAGING }} - SERVER_PRIVATE_KEY: ${{ env.CURRENT_BRANCH == 'production' && secrets.PROD_SERVER_PRIVATE_KEY || secrets.STAGING_SERVER_PRIVATE_KEY }} - STORAGE_URL: ${{ env.CURRENT_BRANCH == 'production' && secrets.PROD_STORAGE_URL || secrets.STAGING_STORAGE_URL }} - COMMISSION_FEE: ${{ env.CURRENT_BRANCH == 'production' && secrets.PROD_COMMISSION_FEE || secrets.STAGING_COMMISSION_FEE }} - - name: Authenticate to Google Cloud - uses: google-github-actions/auth@v1 - with: - credentials_json: ${{ secrets.gcp_deploy_creds }} - - - name: Deploy to Cloud Run - uses: google-github-actions/deploy-cloudrun@v1 - with: - metadata: "service.${{ env.CURRENT_BRANCH }}.yaml" - region: us-west1 - - - name: Set Min Instances and Enable CPU Boost - run: | - gcloud run services update ${{ env.CURRENT_BRANCH == 'production' && 'prod-storage' || 'staging-storage' }} \ - --min-instances=1 --cpu-boost --region=us-west1 diff --git a/infra/wab/AGENTS.md b/infra/wab/AGENTS.md index c3fc4c467..da1c81f82 100644 --- a/infra/wab/AGENTS.md +++ b/infra/wab/AGENTS.md @@ -1,161 +1,10 @@ -# CLAUDE.md +# ts-stack agent instructions -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This project follows the repository-wide [agent instructions](../../AGENTS.md) +and [contribution policy](../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -## Project Overview - -The **Wallet Authentication Backend (WAB)** is a TypeScript/Express server that provides multi-factor authentication for BSV wallet applications. It manages **256-bit presentation keys** for users, which are authenticated via various methods (SMS, ID verification, dev console). The system is part of a larger 2-of-3 threshold cryptographic recovery system used by BSV wallet clients. - -## Development Commands - -```bash -# Install dependencies -npm install - -# Development server (auto-restart on changes) -npm run dev - -# Build TypeScript to JavaScript -npm run build - -# Run production server -npm start - -# Database migrations -npm run migrate - -# Run tests with coverage -npm test -``` - -## Architecture - -### Core Flow - -1. **Authentication**: Client requests authentication via `/auth/start` with a presentation key and method type -2. **Verification**: External service (Twilio, DevConsole) verifies user identity -3. **Completion**: Client completes authentication via `/auth/complete` with verification code -4. **Key Storage/Retrieval**: Server either creates new user with presentation key OR retrieves existing user's key based on verified identity (phone number, etc.) - -### Key Components - -#### Auth Methods (`src/auth-methods/`) -Abstract base class `AuthMethod` with implementations: -- **TwilioAuthMethod** - SMS verification via Twilio Verify API -- **DevConsoleAuthMethod** - Development-only OTP logged to console (in-memory state, singleton in AuthController:src/controllers/AuthController.ts:14) -- **PersonaAuthMethod** - Mocked ID verification (not production-ready) - -New auth methods must implement: -- `startAuth()` - Initiate verification flow -- `completeAuth()` - Verify and complete authentication -- `buildConfigFromPayload()` - Extract unique identifier for user lookup -- `isAlreadyLinked()` - Check if method already linked to user (optional) - -#### Controllers (`src/controllers/`) -- **AuthController** - Handles `/auth/start` and `/auth/complete` endpoints. Uses `getAuthMethodInstance()` factory to instantiate appropriate auth method based on `methodType` string. -- **UserController** - List linked methods, unlink methods, delete user -- **FaucetController** - One-time BSV payment using R-puzzles (requires `SERVER_PRIVATE_KEY` and `STORAGE_URL` env vars) -- **InfoController** - Server info endpoint - -#### Services (`src/services/`) -- **UserService** - Database operations for users, auth methods, and faucet payments. User lookup happens via `findUserByConfig()` which searches by methodType + config (e.g., phone number). - -#### Database (`src/db/`) -Uses Knex.js with support for SQLite (dev), MySQL (production): -- **users** - id, presentationKey (unique) -- **auth_methods** - id, userId, methodType, config (stores verified identifier like phone number) -- **payments** - id, userId, beef (transaction bytes), k (R-puzzle key), txid, amount, outputIndex - -### Database Configuration - -Environment-specific config in `src/knexfile.ts`: -- **development**: SQLite at `./dev.sqlite3` -- **test**: In-memory SQLite -- **production**: Uses env vars `DB_CLIENT`, `DB_USER`, `DB_PASS`, `DB_NAME`, `DB_HOST`, `DB_PORT` - -Cloud SQL connections: Use `DB_CONNECTION_NAME` for GCP Cloud SQL with Unix socket format. - -### Critical Authentication Logic - -**User Identification**: Users are NOT identified by presentation key during authentication. Instead: -1. Auth method verifies external identity (phone number, etc.) -2. `buildConfigFromPayload()` extracts unique identifier (e.g., `phoneNumber`) -3. `UserService.findUserByConfig(methodType, config)` looks up existing user -4. If found: return that user's stored presentation key (NOT the one client sent) -5. If not found: create new user with client's presentation key - -This allows same user to authenticate from different devices with same phone number and retrieve their original presentation key. - -### Faucet System - -Creates BSV transactions using: -- **R-puzzles** - Bitcoin script type where outputs are unlocked with a specific value (k) -- **@bsv/wallet-toolbox** - BSV SDK for transaction creation and broadcasting -- Requires `SERVER_PRIVATE_KEY` (root key for wallet) and `STORAGE_URL` (overlay services endpoint) -- One payment per user, subsequent requests return existing payment data - -## Environment Variables - -Required for development: -```bash -# Twilio (if using TwilioAuthMethod) -TWILIO_ACCOUNT_SID=ACxxxx -TWILIO_AUTH_TOKEN=xxxx -TWILIO_VERIFY_SERVICE_SID=VAxxxx or VExxxx - -# Faucet (if using /faucet/request) -SERVER_PRIVATE_KEY= -STORAGE_URL= -COMMISSION_FEE=1000 # satoshis - -# Production Database -DB_CLIENT=pg or mysql2 -DB_USER=username -DB_PASS=password -DB_NAME=database -DB_HOST=localhost -DB_PORT=5432 -DB_CONNECTION_NAME= - -# Server -PORT=3000 -NODE_ENV=development|production -``` - -## Testing - -- Test files: `src/__tests/*.test.ts` -- Uses Jest with ts-jest preset -- In-memory SQLite database for tests -- Run with `npm test` for coverage reports - -## Integration Context - -WAB is part of a larger wallet authentication system: -- **WabClient** - Client library that calls WAB endpoints -- **wallet-toolbox** - Contains `WalletAuthenticationManager` and UMP (User Management Protocol) token system -- **2-of-3 Recovery System** - Presentation key is 1 of 3 authentication factors (along with password and recovery key) used to derive wallet root keys through XOR and symmetric encryption - -See `how-it-works.md` for detailed explanation of the cryptographic recovery system. - -## API Endpoints - -- `GET /info` - Server configuration info -- `POST /auth/start` - Start authentication (body: `methodType`, `presentationKey`, `payload`) -- `POST /auth/complete` - Complete authentication (body: `methodType`, `presentationKey`, `payload`) -- `POST /user/linkedMethods` - List user's linked auth methods (body: `presentationKey`) -- `POST /user/unlinkMethod` - Unlink auth method (body: `presentationKey`, `methodId`) -- `POST /user/delete` - Delete user account (body: `presentationKey`) -- `POST /faucet/request` - Request faucet payment (body: `presentationKey`) - -## CORS - -Fully permissive CORS configuration in `src/app.ts:11-22` - all origins, headers, and methods allowed. Handles preflight OPTIONS requests. - -## Deployment - -See README.md for: -- Docker containerization -- Google Cloud Run deployment with Cloud SQL -- GitHub Actions CI/CD with Workload Identity Federation -- Environment variable management for production +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/infra/wallet-infra/.github/workflows/build-and-push.yml b/infra/wallet-infra/.github/workflows/build-and-push.yml deleted file mode 100644 index 18368fa8a..000000000 --- a/infra/wallet-infra/.github/workflows/build-and-push.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Build and Push to AWS Marketplace ECR - -on: - workflow_dispatch: - inputs: - version: - description: 'Version tag (e.g., 1.0.0)' - required: true - default: '1.0.0' - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: us-east-1 - - - name: Login to Amazon ECR - run: | - aws ecr get-login-password --region us-east-1 | \ - docker login --username AWS --password-stdin \ - 709825985650.dkr.ecr.us-east-1.amazonaws.com - - - name: Build Docker image - run: | - docker build --platform linux/amd64 -t wallet-infra:${{ github.event.inputs.version }} . - - - name: Tag image - run: | - docker tag wallet-infra:${{ github.event.inputs.version }} \ - 709825985650.dkr.ecr.us-east-1.amazonaws.com/bsv-blockchain/wallet-infra:${{ github.event.inputs.version }} - - - name: Push to ECR - run: | - docker push 709825985650.dkr.ecr.us-east-1.amazonaws.com/bsv-blockchain/wallet-infra:${{ github.event.inputs.version }} - - - name: Verify push - run: | - aws ecr describe-images \ - --registry-id 709825985650 \ - --repository-name bsv-blockchain/wallet-infra \ - --region us-east-1 \ - --image-ids imageTag=${{ github.event.inputs.version }} diff --git a/infra/wallet-infra/.github/workflows/build.yaml b/infra/wallet-infra/.github/workflows/build.yaml deleted file mode 100644 index d00629659..000000000 --- a/infra/wallet-infra/.github/workflows/build.yaml +++ /dev/null @@ -1,66 +0,0 @@ -name: Build and push OCI image to Docker Hub - -on: - push: - tags: - - 'v*' - -jobs: - check-current-branch: - runs-on: ubuntu-latest - outputs: - branch: ${{ steps.check_step.outputs.branch }} - steps: - - name: Checkout - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 - with: - fetch-depth: 0 - - - name: Get current branch - id: check_step - # 1. Get the list of branches ref where this tag exists - # 2. Remove 'origin/' from that result - # 3. Put that string in output - run: | - raw=$(git branch -r --contains ${{ github.ref }}) - branch="$(echo ${raw//origin\//} | tr -d '\n')" - echo "branch=$branch" >> $GITHUB_OUTPUT - echo "Branches where this tag exists : $branch." - - image: - runs-on: ubuntu-latest - needs: check-current-branch - if: ${{ contains(needs.check-current-branch.outputs.branch, 'master') }} - steps: - - name: Check out the repo - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Get build args - id: build_args - run: | - echo "APP_COMMIT=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - echo "APP_VERSION=$(git describe --tags --always --abbrev=0 --match='v[0-9]*.[0-9]*.[0-9]*' 2> /dev/null | sed 's/^.//')" >> "$GITHUB_OUTPUT" - - - name: Log in to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) - id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 - with: - images: bsvb/wallet-infra - - - name: Build and push image - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 - with: - context: . - file: ./Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - APP_COMMIT=${{ steps.build_args.outputs.APP_COMMIT }} - APP_VERSION=${{ steps.build_args.outputs.APP_VERSION }} diff --git a/infra/wallet-infra/AGENTS.md b/infra/wallet-infra/AGENTS.md index d24fc0f7e..da1c81f82 100644 --- a/infra/wallet-infra/AGENTS.md +++ b/infra/wallet-infra/AGENTS.md @@ -1,89 +1,10 @@ -# CLAUDE.md — Wallet Infra (UTXO Management Server) +# ts-stack agent instructions -## Purpose -A reference implementation of BSV wallet infrastructure for secure UTXO storage and management. Provides HTTP JSON-RPC endpoints for wallet clients to store/retrieve transaction outputs, track spent/unspent states, manage baskets and labels, and store certificate data. Enforces mutual authentication and supports optional micropayment pricing. Built on `@bsv/wallet-toolbox` and fully customizable for production deployments. +This project follows the repository-wide [agent instructions](../../AGENTS.md) +and [contribution policy](../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -## Service surface -- **JSON-RPC endpoint** – `POST /` with methods for wallet operations (BRC-100 compliant) - - `walletUtxoStorage_getHeight` – Current blockchain height - - `walletUtxoStorage_listOutputs` – Query UTXOs with filters - - `walletUtxoStorage_insertOutput` – Store transaction output - - `walletUtxoStorage_updateOutput` – Modify output metadata - - `walletUtxoStorage_listBaskets` – Query output baskets - - `walletUtxoStorage_createBasket` – Create named output group - - And dozens more (see @bsv/wallet-toolbox docs) -- **Admin endpoints** (if configured) - - Health checks, monitoring, metrics -- **WebSocket** – Optional real-time UTXO update notifications (not in base config) - -## Real deployment -- **Dockerfile** – Digest-pinned Alpine-based Node 24 image. Compiles TypeScript to `out/`, runs `node out/src/index.js` with nginx reverse proxy support -- **docker-compose.yml** – Services: Node app (on port 8081), MySQL 8.0 database with health checks -- **nginx.conf** – Optional reverse proxy (if ENABLE_NGINX=true), listening on 8080, proxying to app on 8081 -- **Knex migrations** – Auto-applied on startup via `@bsv/wallet-toolbox` - - Creates tables: outputs, baskets, labels, certificates, metadata - - Indexes on identity_key, output_hash, blockchain_height for query performance - -## Configuration -Environment variables (from `.env.example`): -- **NODE_ENV** – `development` or `production` -- **HTTP_PORT** – Express server port (default: 8081, use 8081 if nginx is enabled on 8080) -- **ENABLE_NGINX** – Set to `'true'` to start nginx reverse proxy on port 8080 (default: false) -- **BSV_NETWORK** – Target blockchain network (`main`, `test`, or `regtest`) -- **SERVER_PRIVATE_KEY** – 256-bit hex private key for server identity (required) -- **KNEX_DB_CONNECTION** – Knex database connection JSON string - - Example: `{"port":3306,"host":"mysql","user":"root","password":"rootPass","database":"wallet_storage"}` -- **COMMISSION_FEE** – Optional commission fee in satoshis per request (default: 0) -- **COMMISSION_PUBLIC_KEY** – Public key to receive commission payments (if COMMISSION_FEE > 0) -- **FEE_MODEL** – Fee calculation model as JSON (default: `{"model":"sat/kb","value":1}`) -- **TAAL_API_KEY** – API key for Taal blockchain data service (optional) - -## Dependencies -- **Database** – MySQL 8.0 via Knex + mysql2 driver (other Knex-supported DBs can be substituted) -- **@bsv packages** - - `@bsv/wallet-toolbox` – Core UTXO storage, wallet operations, migrations - - `@bsv/sdk` – Cryptography, key operations, transaction handling - - `@bsv/auth-express-middleware` – BRC-103 mutual authentication - - `@bsv/payment-express-middleware` – Optional payment verification for API calls -- **External services** - - Blockchain data (via Taal or ARC) – Transaction broadcasting, blockchain height queries - - Optional: Monitoring/observability (CloudWatch, Datadog, etc.) -- **Key packages** – Express, body-parser, dotenv, knex, mysql2 - -## Operational concerns -- **Local dev** – `npm run dev` (uses ts-node) requires MySQL running (use docker-compose up) -- **Build** – `npm run build` compiles TypeScript to `out/` -- **Production** – Docker container or direct `node out/src/index.js` with MySQL connection -- **Migrations** – Auto-run on startup; Knex manages schema versioning -- **Scaling** – Stateless design; multiple instances can share same MySQL database with connection pooling -- **Database** – MySQL 8.0 required; ensure adequate indexing on identity_key, output_hash, and blockchain_height -- **Authentication** – BRC-103 mutual auth on all JSON-RPC calls; enforces identity-based access control -- **Payment enforcement** – Optional via `@bsv/payment-express-middleware`; can charge per-call or per-route -- **Performance** – Consider database query caching, connection pooling tuning, and output batch operations for high-volume deployments -- **Nginx** – Optional reverse proxy for load balancing, SSL termination (ENABLE_NGINX=true adds nginx on port 8080) - -## Spec conformance -- **BRC-100** – Full JSON-RPC wallet interface for UTXO storage and management -- **BRC-103** – Mutual authentication on all API calls -- **BRC-105** – Optional envelope support for multi-sig authorization -- **JSON-RPC 2.0** – Standard JSON-RPC protocol on POST / - -## Integration points -- **BSV wallet clients** – Use this server for UTXO storage, retrieval, and blockchain queries -- **Wallet Toolbox** – Storage implementation extends `@bsv/wallet-toolbox` base classes -- **Blockchain services** – Integrates with Taal or ARC for fee estimation and transaction submission -- **Payment processors** – Optional payment middleware for micropayment collection -- **Overlay network** – Can advertise wallet storage service capability - -## File map -- **src/** - - `index.ts` – Entry point: initializes Knex, wallet-toolbox, StorageServer, starts HTTP server - - **No app.ts** – Configuration is inline in index.ts via wallet-toolbox classes -- **out/** – Compiled TypeScript output (created by `npm run build`) -- **Dockerfile** – Digest-pinned Alpine-based Node 24 with nginx optional support -- **docker-compose.yml** – App + MySQL 8.0 -- **nginx.conf** – Reverse proxy config (used if ENABLE_NGINX=true) -- **.env.example** – Template with all required/optional variables -- **tsconfig.json** – TypeScript configuration -- **package.json** – Scripts: `build`, `start`, `dev` -- **guides/** – Deployment guides (local_development.md, gcloud_deployment.md for Cloud Run) +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/package.json b/package.json index 9bfb014aa..6004c3d05 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "typecheck": "pnpm -r --workspace-concurrency=4 --filter '!@bsv/ts-stack' --if-present run typecheck", "test": "pnpm -r --filter '!@bsv/ts-stack' run test", "lint": "oxlint . --ignore-path .oxlintignore --deny-warnings", - "format:root": "prettier --check \"package.json\" \"pnpm-workspace.yaml\" \"tsconfig.base.json\" \".prettierrc.json\" \"scripts/**/*.{mjs,json}\" \"governance/**/*.{json,md,mjs}\" \".github/**/*.{md,yml,yaml}\"", + "format:root": "prettier --check \"AGENTS.md\" \"CLAUDE.md\" \"CODE_OF_CONDUCT.md\" \"CONTRIBUTING.md\" \"package.json\" \"pnpm-workspace.yaml\" \"tsconfig.base.json\" \".prettierrc.json\" \"scripts/**/*.{mjs,json}\" \"governance/**/*.{json,md,mjs}\" \".github/**/*.{md,yml,yaml}\"", "format:check": "pnpm format:root && pnpm -r --filter '!@bsv/ts-stack' --if-present run format:check", "clean": "pnpm -r --filter '!@bsv/ts-stack' run clean", "sync-versions": "node scripts/sync-versions.mjs", @@ -20,8 +20,10 @@ "check-versions": "node scripts/check-versions.mjs", "typescript:check": "node scripts/typescript-toolchain.mjs", "health:baseline": "node scripts/repository-health.mjs --update-contract-baseline", - "health:check": "node --test scripts/*.test.mjs && pnpm docs:facts:check && pnpm ops:check && node scripts/dependency-release-governance.mjs check && node scripts/browser-artifact-governance.mjs && node scripts/typescript-toolchain.mjs && node scripts/package-license-policy.mjs && node scripts/sync-service-rate-limit-policy.mjs --check && node scripts/sync-service-edge-policy.mjs --check && node scripts/repository-health.mjs", + "health:check": "node --test scripts/*.test.mjs && pnpm contributor-policy:check && pnpm docs:facts:check && pnpm ops:check && node scripts/dependency-release-governance.mjs check && node scripts/browser-artifact-governance.mjs && node scripts/typescript-toolchain.mjs && node scripts/package-license-policy.mjs && node scripts/sync-service-rate-limit-policy.mjs --check && node scripts/sync-service-edge-policy.mjs --check && node scripts/repository-health.mjs", "health:report": "node scripts/repository-health.mjs --format markdown", + "contributor-policy:check": "node scripts/contributor-policy.mjs", + "contributor-policy:sync": "node scripts/contributor-policy.mjs --write", "test:governance": "node scripts/test-governance.mjs", "build:property": "pnpm -r --filter '!@bsv/ts-stack' --filter '!docs-site' --filter '!example-paymail' run build", "test:property": "pnpm build:property && pnpm -r --workspace-concurrency=4 --if-present run test:property", diff --git a/packages/helpers/amountinator/.github/workflows/push.yaml b/packages/helpers/amountinator/.github/workflows/push.yaml deleted file mode 100644 index c93f87e49..000000000 --- a/packages/helpers/amountinator/.github/workflows/push.yaml +++ /dev/null @@ -1,73 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/helpers/amountinator/AGENTS.md b/packages/helpers/amountinator/AGENTS.md index 408a00c6c..dcea67c80 100644 --- a/packages/helpers/amountinator/AGENTS.md +++ b/packages/helpers/amountinator/AGENTS.md @@ -1,123 +1,10 @@ -# CLAUDE.md — @bsv/amountinator +# ts-stack agent instructions -## Purpose (1-2 sentences) +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -Satoshi/BSV/USD and multi-fiat currency conversion with exchange rate caching and wallet settings integration. Converts amounts between crypto (SATS, BSV) and 15+ fiat currencies with auto-refresh and user preference persistence. - -## Public API surface - -### CurrencyConverter -- `constructor(refreshInterval?: number, settingsManager?: WalletSettingsManager)` — Create converter; refreshInterval in ms (default: 5 min), 0 = no auto-refresh -- `async initialize(): Promise` — Fetch exchange rates and preferred currency; start auto-refresh timer if enabled -- `dispose(): void` — Stop auto-refresh timer (cleanup) -- `async fetchExchangeRates(force?: boolean): Promise` — Get USD/BSV and fiat rates -- `getCurrencySymbol(): string` — Return symbol for preferred currency (e.g., "$", "€", "¥") -- `async convertAmount(amount: number | string, formatOptions?: FormatOptions): Promise` — Parse amount string (auto-detect currency), convert to preferred currency, format -- `async convertToSatoshis(amount: number): Promise` — Convert from preferred currency to satoshis (rounded up) -- `convertCurrency(amount: number, fromCurrency: string, toCurrency: string): number | null` — Low-level conversion (no async, no formatting) -- `exchangeRates: ExchangeRates` (property) — Current rates { usdPerBsv, fiatPerUsd: {USD, GBP, EUR, ...} } -- `preferredCurrency: SupportedCurrencyCode` (property) — User's preferred currency - -### Utilities -- `formatAmountWithCurrency(amount: number, currency: SupportedCurrencyCode, options?: FormatOptions): FormattedAmount` — Format with symbol, decimals, hover text - -### Types -- `SupportedCurrencyCode` — 'SATS' | 'BSV' | FiatCurrencyCode -- `FiatCurrencyCode` — 'USD' | 'GBP' | 'EUR' | 'JPY' | 'CNY' | 'INR' | 'AUD' | 'CAD' | 'CHF' | 'HKD' | 'SGD' | 'NZD' | 'SEK' | 'NOK' | 'MXN' -- `ExchangeRates` — { usdPerBsv: number, fiatPerUsd: Record } -- `FormatOptions` — { decimals?: number, grouping?: boolean } -- `FormattedAmount` — { display: string, hoverText: string, value: number } - -## Real usage patterns - -```typescript -// 1. Initialize and convert using preferred currency -import { CurrencyConverter } from '@bsv/amountinator' - -const converter = new CurrencyConverter() // 5-min auto-refresh -await converter.initialize() - -// Auto-detect input currency, convert to user's preference -const formatted = await converter.convertAmount('5000') // "5000" -> assumes SATS or BSV -console.log(formatted.display) // e.g. "£3.10" -console.log(formatted.hoverText) // e.g. "0.00005000 BSV" - -// 2. Convert to specific currency -const usdAmount = converter.convertCurrency(0.1, 'BSV', 'USD') // 6.2 (if usdPerBsv = 62) - -// 3. Get currency symbol -const symbol = converter.getCurrencySymbol() // "$" if USD, "€" if EUR, etc. - -// 4. Fetch fresh rates on demand -const rates = await converter.fetchExchangeRates(true) // force = true -console.log(rates.usdPerBsv) - -// 5. Convert from user's preferred currency to satoshis -const sats = await converter.convertToSatoshis(10) // If preferred = 'USD', converts USD→SATS -console.log(sats) // e.g. 1610000 - -// 6. Cleanup (stop auto-refresh) -converter.dispose() - -// 7. No auto-refresh variant -const staticConverter = new CurrencyConverter(0) // refreshInterval = 0 -await staticConverter.initialize() -const amount = await staticConverter.convertAmount('100') -// Rates will not auto-update -``` - -## Key concepts - -- **Exchange Rates** — Two-tier: USD/BSV (from external service), then USD↔fiat (15+ currencies) -- **Preferred Currency** — User's chosen display currency; persisted in wallet settings -- **Auto-Refresh** — Rates updated on interval; caches within window to avoid redundant fetches -- **Currency Detection** — `convertAmount()` auto-detects if input is "100" (SATS) vs "0.1" (BSV) vs "10 USD" (includes currency suffix) -- **Wallet Settings Integration** — Reads/writes preferred currency to wallet's settings manager -- **Fiat Conversion** — Single conversion path: SATS ↔ USD ↔ Fiat (via usdPerBsv and fiatPerUsd) -- **Rounding** — `convertToSatoshis()` rounds up (Math.ceil) to ensure sufficient payment - -## Dependencies - -**Runtime:** -- `@bsv/sdk` ^2.1.6 -- `@bsv/wallet-toolbox-client` ^2.4.4 (Services, WalletSettingsManager) - -**Dev:** -- TypeScript, Jest, ts-jest - -## Common pitfalls / gotchas - -1. **Rates not available** — `convertCurrency()` throws if `usdPerBsv <= 0` or fiat rate is missing; call `initialize()` first -2. **Preferred currency not set** — If settings fetch fails, defaults to 'SATS'; user can manually set `converter.preferredCurrency` -3. **Auto-refresh timer not stopped** — Always call `dispose()` before component unmount to prevent memory leaks -4. **String parsing ambiguity** — "100.5" is parsed as BSV, "100" as SATS (heuristic: decimal point = BSV) -5. **Rounding behavior** — `convertToSatoshis()` always rounds UP; use `convertCurrency()` directly for exact values -6. **Promise caching** — If two calls to `fetchExchangeRates()` happen simultaneously, both await the same promise (deduplication) -7. **Wallet settings manager required** — If you don't provide `settingsManager` in constructor, it creates one internally; ensure wallet client is available - -## Spec conformance - -- **Currency codes** — ISO 4217 (e.g., USD, GBP, EUR) -- **Satoshi/BSV units** — 1 BSV = 100,000,000 SATS -- **Fiat conversion** — Generic multi-currency (no BRC reference) - -## File map - -``` -amountinator/ - src/ - index.ts # Exports - utils/ - currencyConverter.ts # CurrencyConverter class - amountFormatHelpers.ts # formatAmountWithCurrency function - types.ts (implied) # Type definitions (ExchangeRates, SupportedCurrencyCode, etc.) - tests/ - currencyConverterCache.test.ts # Cache behavior, rate updates - formatAmountWithCurrency.test.ts # Formatting and display logic -``` - -## Integration points - -- **Depends on:** `@bsv/sdk` (WalletClient), `@bsv/wallet-toolbox-client` (Services for rate fetch, WalletSettingsManager for preferences) -- **Used by:** UI applications needing multi-currency display (React, Vue, etc.), wallets, faucets, payment forms -- **Complements:** `@bsv/fund-wallet` (faucet may display amounts in user's preferred currency), any app showing BSV amounts to users +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/helpers/bsv-wallet-helper/.github/ISSUE_TEMPLATE/bug_report.md b/packages/helpers/bsv-wallet-helper/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 4fc56f13f..000000000 --- a/packages/helpers/bsv-wallet-helper/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: Bug Report -about: Report a bug or an issue you've found with `@bsv/sdk`. -title: "[BUG]" -labels: bug -assignees: '' - ---- - -## Bug Description - -Briefly describe the bug/issue you've encountered. - -## Steps to Reproduce - -1. Step 1 -2. Step 2 -3. ... - -## Expected Behavior - -What should have happened if the bug hadn't occurred? - -## Actual Behavior - -What actually happened? - -## Stack Traces or Screenshots - -If applicable, add screenshots or stack traces to help explain the issue. - -## Environment - -- OS: [e.g. MacOS, Windows] -- Node version: [e.g. 12.20] -- `@bsv/sdk` version: [e.g. 1.2.3] - -## Additional Information - -Provide any additional context or information about the bug. diff --git a/packages/helpers/bsv-wallet-helper/.github/ISSUE_TEMPLATE/discussion.md b/packages/helpers/bsv-wallet-helper/.github/ISSUE_TEMPLATE/discussion.md deleted file mode 100644 index 52aa083ce..000000000 --- a/packages/helpers/bsv-wallet-helper/.github/ISSUE_TEMPLATE/discussion.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Discussion -about: Propose a discussion or seek clarification about a feature or topic. -title: "[DISCUSSION]" -labels: discussion -assignees: '' - ---- - -## Summary - -Briefly describe the topic you'd like to discuss. - -## Motivation - -Why do you believe this to be important? - -## Description - -Provide a detailed description or elaborate on your topic. - -## Additional References - -Provide any additional articles, links, or context that would help facilitate the discussion. diff --git a/packages/helpers/bsv-wallet-helper/.github/pull_request_template.md b/packages/helpers/bsv-wallet-helper/.github/pull_request_template.md deleted file mode 100644 index c6182f7f8..000000000 --- a/packages/helpers/bsv-wallet-helper/.github/pull_request_template.md +++ /dev/null @@ -1,25 +0,0 @@ -## Description of Changes - -Provide a brief description of the changes you've made. - -## Linked Issues / Tickets - -Reference any related issues or tickets, e.g. "Closes #123". - -## Testing Procedure - -Describe the tests you've added or any testing steps you've taken. - -- [ ] I have added new unit tests -- [ ] All tests pass locally -- [ ] I have tested manually in my local environment - -## Checklist - -- [ ] I have performed a self-review of my own code -- [ ] I have made corresponding changes to the documentation -- [ ] My changes generate no new warnings -- [ ] I have updated `CHANGELOG.md` with my changes -- [ ] I have run `npm run doc` and `npm run lint` one final time before requesting a review -- [ ] I have fixed all linter errors to ensure these changes are compliant with `ts-standard` -- [ ] I have run `npm version patch` so that my changes will trigger a new version to be released when they are merged diff --git a/packages/helpers/bsv-wallet-helper/.github/workflows/push.yaml b/packages/helpers/bsv-wallet-helper/.github/workflows/push.yaml deleted file mode 100644 index 8b52a0ac4..000000000 --- a/packages/helpers/bsv-wallet-helper/.github/workflows/push.yaml +++ /dev/null @@ -1,72 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - -jobs: - test: - permissions: - contents: read - name: Build & Test - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - permissions: - id-token: write # Required for OIDC - contents: read - runs-on: ubuntu-latest - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/helpers/bsv-wallet-helper/AGENTS.md b/packages/helpers/bsv-wallet-helper/AGENTS.md index 1a87d29a7..dcea67c80 100644 --- a/packages/helpers/bsv-wallet-helper/AGENTS.md +++ b/packages/helpers/bsv-wallet-helper/AGENTS.md @@ -1,202 +1,10 @@ -# CLAUDE.md — @bsv/wallet-helper +# ts-stack agent instructions -## Purpose (1-2 sentences) +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -Fluent transaction builder and wallet-compatible script templates for BSV. Provides high-level APIs for constructing multi-output transactions (P2PKH, ordinals, custom), managing inputs, and handling BRC-29 key derivation without exposing private keys. - -## Public API surface - -### TransactionBuilder -- `new TransactionBuilder(wallet: WalletInterface, description?: string)` — Fluent transaction builder -- `.addP2PKHOutput(params: AddP2PKHOutputParams): OutputBuilder` — Add pay-to-pubkey-hash output -- `.addOrdinalP2PKHOutput(params: AddOrdinalP2PKHOutputParams): OutputBuilder` — Add 1Sat ordinal with inscription/MAP metadata -- `.addCustomOutput(params: AddCustomOutputParams): OutputBuilder` — Add raw locking script output -- `.addChangeOutput(params: AddChangeOutputParams): OutputBuilder` — Auto-calculate change with fee deduction -- `.addP2PKHInput(params: AddP2PKHInputParams): InputBuilder` — Spend P2PKH UTXO -- `.addOrdLockInput(params: AddOrdLockInputParams): InputBuilder` — Spend OrdLock (marketplace listing) -- `.addCustomInput(params: AddCustomInputParams): InputBuilder` — Spend custom locking script -- `.options(opts: TransactionOptions): TransactionBuilder` — Set transaction-level flags (randomizeOutputs, trustSelf, etc.) -- `.build(params?: BuildParams): Promise` — Execute transaction or preview - -### OutputBuilder -- `.basket(name: string): OutputBuilder` — Assign output to basket -- `.customInstructions(data: string): OutputBuilder` — Attach app-specific metadata -- Return type is both OutputBuilder and TransactionBuilder (chaining) - -### Script Templates -- `WalletP2PKH` — P2PKH with wallet derivation or direct pubkey - - `.lock(params: P2PKHLockParams): Promise` - - `.unlock(params: P2PKHUnlockParams): UnlockingTemplate` -- `WalletOrdP2PKH` — 1Sat Ordinal with inscription data and MAP metadata - - `.lock(params: OrdinalLockParams): Promise` - - `.unlock(params: OrdinalUnlockParams): UnlockingTemplate` -- `WalletOrdLock` — Marketplace listing (seller cancel / buyer purchase paths) - - `.lock(params: OrdLockLockParams): Promise` - - `.unlock(params: OrdLockUnlockParams): UnlockingTemplate` - -### Utilities -- `makeWallet(chain, storageURL, privateKeyHex): Promise` — Create live BRC-100 wallet from private key (network-backed, use for explicit integration tests only) -- `makeMockWallet(privateKey): Promise` — Create hermetic in-memory mock wallet (ProtoWallet, no network; for unit tests) -- `calculatePreimage(tx, inputIndex, prevOutScript): Promise` — Compute signature preimage -- `addOpReturnData(script: LockingScript, data: string[]): LockingScript` — Append OP_RETURN metadata -- `getDerivation(wallet, protocolID, keyID, counterparty): Promise` — Derive BRC-29 key -- `getAddress(publicKey): string` — Convert pubkey to P2PKH address -- `isP2PKH(script): boolean` — Identify P2PKH script -- `isOrdinal(script): boolean` — Check for ordinal inscription -- `hasOrd(script): boolean` — Has ord marker -- `hasOpReturnData(script): boolean` — Contains OP_RETURN -- `getScriptType(script): ScriptType` — Classify script (p2pkh, ordinal, custom, etc.) -- `extractOpReturnData(script): string[]` — Parse OP_RETURN payloads -- `extractMapMetadata(script): MAP` — Extract MAP (Magic Attribute Protocol) metadata -- `extractInscriptionData(script): InscriptionData` — Decode ordinal inscription - -## Real usage patterns - -```typescript -// 1. Build and send P2PKH transaction with metadata -import { TransactionBuilder } from '@bsv/wallet-helper' - -const result = await new TransactionBuilder(wallet, "Payment with metadata") - .addP2PKHOutput({ - publicKey: recipientKey, - satoshis: 5000, - description: "Payment to Bob" - }) - .addOpReturn(['APP_ID', JSON.stringify({ memo: 'Thanks!' })]) - .build() - -console.log(`Sent: ${result.txid}`) - -// 2. Multi-output transaction with auto-calculated change -await new TransactionBuilder(wallet, "Multi-output with change") - .addP2PKHOutput({ publicKey: alice, satoshis: 1000 }) - .addP2PKHOutput({ publicKey: bob, satoshis: 2000 }) - .addChangeOutput({ description: "Change" }) - .build() - -// 3. Transaction with BRC-29 automatic derivation (no pubkey) -await new TransactionBuilder(wallet, "Auto-derived") - .addP2PKHOutput({ satoshis: 1000 }) // Uses automatic derivation - .basket("my-basket") - .customInstructions("app-data") - .build() - -// 4. Spend UTXOs from previous transaction -await new TransactionBuilder(wallet, "Spend UTXO") - .addP2PKHInput({ sourceTransaction, sourceOutputIndex: 0, description: "UTXO" }) - .addP2PKHOutput({ publicKey: recipient, satoshis: 500 }) - .build() - -// 5. Create 1Sat ordinal with inscription and metadata -const ordResult = await new TransactionBuilder(wallet, "Mint ordinal") - .addOrdinalP2PKHOutput({ - walletParams: { protocolID: [2, 'p2pkh'], keyID: '0', counterparty: 'self' }, - satoshis: 1, - inscription: { - dataB64: Buffer.from('Hello ordinals').toString('base64'), - contentType: 'text/plain' - }, - metadata: { app: 'gallery', type: 'greeting', author: 'Alice' } - }) - .build() - -// 6. Preview transaction before execution -const preview = await new TransactionBuilder(wallet) - .addP2PKHOutput({ publicKey: someKey, satoshis: 5000 }) - .build({ preview: true }) - -console.log('Would send:', preview.estimatedFee) -``` - -## Key concepts - -- **Fluent API** — Method chaining for readable transaction construction -- **BRC-29 Derivation** — Automatic hierarchical key derivation (protocolID, keyID, counterparty); omit publicKey to enable -- **Wallet-Compatible** — Never exposes private keys; always uses wallet's `createAction` / `signAction` -- **Change Calculation** — Auto-computes change = inputs - outputs - fees -- **Basket** — Logical grouping of outputs for wallet organization -- **CustomInstructions** — JSON metadata per output; auto-includes derivation info when BRC-29 is used -- **BEEF** — Broadcast-Everything-BEEF transaction format for secure input proofs -- **Lock/Unlock Consistency** — Must use same derivation params for both lock and unlock operations -- **MAP Metadata** — Magic Attribute Protocol for ordinal inscriptions (app, type, custom fields) -- **OrdLock** — Two-spend-path ordinal marketplace listing (seller cancel, buyer purchase) - -## Dependencies - -**Runtime:** -- `@bsv/sdk` ^2.0.14 -- `@bsv/wallet-toolbox-client` ^2.1.18 - -**Dev:** -- TypeScript, Jest, ts-jest, Oxlint - -## Common pitfalls / gotchas - -1. **Lock/Unlock key mismatch** — If you lock with `{ walletParams: {...} }` but try to unlock with `{ publicKey: ... }`, it fails. Use matching derivation params. -2. **Omitting satoshis on P2PKH** — If you don't provide satoshis and no changeOutput, transaction will fail during build. -3. **UTXO input without outputs** — If you add inputs, you must add outputs; builder doesn't auto-create outputs. -4. **publicKey vs walletParams** — Choose one per output; don't mix. Wallet derivation (walletParams) is preferred for security. -5. **Derivation parameters stored separately** — Store derivation params alongside locking script if you need to spend later. -6. **MAP metadata encoding** — MAP fields must be key-value strings; nested objects are not standard. -7. **Randomized outputs** — By default, outputs are randomized to improve privacy; set `options({ randomizeOutputs: false })` for specific output ordering (e.g., OrdLock purchase). - -## Spec conformance - -- **BRC-29** — Hierarchical key derivation -- **BRC-42** — Public key derivation (from wallet) -- **BRC-100** — Wallet interface (createAction, signAction) -- **BRC-95** — PushDrop (tokens, ordinals) -- **MAP** — Magic Attribute Protocol for ordinal metadata -- **1Sat Ordinals** — Inscription format (dataB64, contentType) -- **OrdLock** — Marketplace listing standard - -## File map - -``` -bsv-wallet-helper/ - src/ - index.ts # Main exports - transaction-builder/ - index.ts # TransactionBuilder exports - transaction.ts # TransactionBuilder, OutputBuilder, InputBuilder classes - types/ - build-params.ts # BuildParams type - output-config.ts # Output configuration types - input-config.ts # Input configuration types - params.ts # Parameter type definitions - type-guards.ts # Type checking utilities - __tests__/ - transaction.test.ts # Transaction builder tests - script-templates/ - index.ts # Script template exports - p2pkh.ts # WalletP2PKH class - ordinal.ts # WalletOrdP2PKH class - ordlock.ts # WalletOrdLock class - types/ - index.ts # Template type exports - params.ts # Lock/unlock parameter types - __tests__/ - p2pkh.test.ts # P2PKH tests - ordinal.test.ts # Ordinal tests - ordlock.test.ts # OrdLock tests - types/ - wallet.ts # WalletDerivationParams, Inscription, MAP types - utils/ - index.ts # Utility exports - derivation.ts # BRC-29 key derivation - opreturn.ts # OP_RETURN parsing/building - scriptValidation.ts # Script type detection - createPreimage.ts # Signature preimage calculation - mockWallet.ts # Test wallet stub - constants.ts # Protocol constants - __tests__/ - derivation.test.ts # Derivation tests - opreturn.test.ts # OP_RETURN tests - scriptValidation.test.ts # Validation tests -``` - -## Integration points - -- **Depends on:** `@bsv/sdk` (Transaction, LockingScript, PublicKey), `@bsv/wallet-toolbox-client` (BRC-29 protocol ID) -- **Used by:** `@bsv/simple` (high-level wallet operations), applications building complex transaction workflows -- **Complements:** `@bsv/templates` (low-level script templates like P2MSKH), `@bsv/simple/server` (server-side wallet) +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/helpers/create-bsv-app/AGENTS.md b/packages/helpers/create-bsv-app/AGENTS.md new file mode 100644 index 000000000..dcea67c80 --- /dev/null +++ b/packages/helpers/create-bsv-app/AGENTS.md @@ -0,0 +1,10 @@ +# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/helpers/did-client/.github/workflows/push.yaml b/packages/helpers/did-client/.github/workflows/push.yaml deleted file mode 100644 index c93f87e49..000000000 --- a/packages/helpers/did-client/.github/workflows/push.yaml +++ /dev/null @@ -1,73 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/helpers/did-client/AGENTS.md b/packages/helpers/did-client/AGENTS.md index 37b1105ba..dcea67c80 100644 --- a/packages/helpers/did-client/AGENTS.md +++ b/packages/helpers/did-client/AGENTS.md @@ -1,156 +1,10 @@ -# CLAUDE.md — @bsv/did-client +# ts-stack agent instructions -## Purpose (1-2 sentences) +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -DID (Decentralized Identifier) client for BSV blockchain. Creates, revokes, and queries `did:bsv:` tokens stored as PushDrop outputs on-chain, with overlay broadcast and lookup service integration. - -## Public API surface - -### DIDClient -- `constructor(opts?: DIDClientOptions)` — Create client with optional overlay configuration -- `async createDID(serialNumber: string, subject: PubKeyHex, opts?: CreateDIDOpts): Promise` — Mint new DID token as PushDrop output -- `async revokeDID(opts: { serialNumber?: string, outpoint?: string }): Promise` — Spend DID token to revoke (by serial or outpoint) -- `async findDID(query: DIDQuery & PaginationOpts, opts?: FindDIDOpts): Promise` — Query overlay lookup service for DID tokens -- `parseLookupAnswer(ans: LookupAnswer, includeBeef: boolean): DIDRecord[]` — Helper to parse lookup responses - -### Configuration (DIDClientOptions) -- `overlayTopic?: string` — Broadcast topic (default: 'tm_did') -- `overlayService?: string` — Lookup service name (default: 'ls_did') -- `wallet?: WalletInterface` — Custom wallet instance; defaults to new WalletClient() -- `networkPreset?: 'mainnet' | 'testnet' | 'local'` — Network for overlay broadcast/query -- `acceptDelayedBroadcast?: boolean` — Allow delayed broadcast if immediate fails - -### Types -- `DIDRecord` — { txid: string, outputIndex: number, serialNumber: Base64String, beef?: number[] } -- `DIDQuery` — { serialNumber?: Base64String, outpoint?: string } -- `CreateDIDOpts` — { wallet?: WalletInterface, derivationPrefix?: Base64String, derivationSuffix?: Base64String } -- `FindDIDOpts` — { resolver?: LookupResolver, wallet?: WalletInterface, includeBeef?: boolean } -- `PaginationOpts` — { limit?: number, skip?: number, sortOrder?: 'asc' | 'desc', startDate?: string, endDate?: string } - -## Real usage patterns - -```typescript -import { DIDClient } from '@bsv/did-client' -import { WalletClient } from '@bsv/sdk' - -// 1. Initialize client -const wallet = new WalletClient() -const didClient = new DIDClient({ - wallet, - networkPreset: 'mainnet', - overlayTopic: 'tm_did', - overlayService: 'ls_did' -}) - -// 2. Create a DID token -const subjectPublicKey = '02abc123...' // Public key of identity subject -const createResult = await didClient.createDID( - 'sn-12345-abc', // Serial number (arbitrary string) - subjectPublicKey, - { - derivationPrefix: Utils.toBase64(Random(10)), - derivationSuffix: Utils.toBase64(Random(10)) - } -) - -if (createResult.status === 'success') { - console.log(`DID created: ${createResult.txid}`) -} else { - console.error(`Broadcast failed: ${createResult.description}`) -} - -// 3. Find DID tokens on overlay -const foundDIDs = await didClient.findDID( - { - serialNumber: 'sn-12345-abc', - limit: 10 - }, - { includeBeef: true } -) - -console.log(`Found ${foundDIDs.length} DID records`) -foundDIDs.forEach(did => { - console.log(` txid: ${did.txid}, output: ${did.outputIndex}`) -}) - -// 4. Query by outpoint -const byOutpoint = await didClient.findDID({ - outpoint: 'abc123def456.0' -}) - -// 5. Revoke DID by serial number -const revokeResult = await didClient.revokeDID({ - serialNumber: 'sn-12345-abc' -}) - -if (revokeResult.status === 'success') { - console.log(`DID revoked in tx ${revokeResult.txid}`) -} - -// 6. Pagination and filtering -const page1 = await didClient.findDID({ - limit: 50, - skip: 0, - sortOrder: 'desc', - startDate: '2024-01-01', - endDate: '2024-12-31' -}) -``` - -## Key concepts - -- **DID Token** — PushDrop output containing serialNumber, subject, and derivation params -- **Serial Number** — Arbitrary Base64-encoded identifier for the DID -- **Subject** — Public key of the entity the DID represents -- **Derivation Prefix/Suffix** — Random values used in PushDrop key derivation; must be preserved to revoke -- **BEEF** — Complete transaction chain for proof; required for revocation -- **Overlay Broadcast** — Publish DID tokens to SHIP/SLAP overlay network for discoverability -- **Lookup Service** — Query indexed overlay for DIDs by serialNumber or outpoint -- **Revocation** — Spending the DID output burns it (marks as revoked) -- **Wallet Storage** — DIDs stored in wallet basket 'did' with tags for efficient lookup - -## Dependencies - -**Runtime:** -- `@bsv/sdk` ^2.0.14 (WalletClient, Transaction, Utils, PushDrop, TopicBroadcaster, LookupResolver) -- `@bsv/wallet-toolbox-client` ^2.1.18 (implied via @bsv/sdk) - -**Dev:** -- TypeScript, Jest, ts-jest, Oxlint, webpack - -## Common pitfalls / gotchas - -1. **Derivation params not preserved** — If you don't store derivationPrefix and derivationSuffix, you cannot revoke the DID later -2. **Serial number encoding** — Serial number must be Base64-encoded string; UTF-8 strings won't work -3. **Subject public key format** — Must be valid public key hex; invalid format causes lock script failure -4. **No wallet storage** — DIDs are broadcast to overlay but NOT automatically stored in the wallet; create your own tracking if needed -5. **Revoke requires BEEF** — To revoke, the output's complete transaction chain is fetched; if wallet doesn't have it, revoke fails -6. **Overlay availability** — If overlay is down, broadcast may be delayed or fail; `acceptDelayedBroadcast: true` allows retry -7. **Update functionality disabled** — `updateDID()` is commented out in source; currently read-only after creation - -## Spec conformance - -- **did:bsv** — DID method specification (draft) -- **BRC-95** — PushDrop token format (key derivation, encryption) -- **BRC-29** — Hierarchical key derivation -- **SHIP/SLAP** — Overlay network for broadcast and lookup -- **BEEF** — Transaction proof format - -## File map - -``` -did-client/ - src/ - index.ts # DIDClient class and main exports - types/ - index.ts # DIDRecord, DIDQuery type definitions - mod.ts # Main entrypoint (re-exports) - webpack.config.js # Browser bundle configuration - tsconfig.cjs.json # CommonJS build config -``` - -## Integration points - -- **Depends on:** `@bsv/sdk` (WalletClient, Transaction, PushDrop, TopicBroadcaster, LookupResolver), overlay services (SHIP/SLAP) -- **Used by:** Identity systems, DID resolver implementations, any app issuing on-chain identifiers -- **Complements:** `@bsv/simple` (high-level wallet operations), credential issuers (can issue VCs for DID subjects) +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/helpers/did/AGENTS.md b/packages/helpers/did/AGENTS.md new file mode 100644 index 000000000..dcea67c80 --- /dev/null +++ b/packages/helpers/did/AGENTS.md @@ -0,0 +1,10 @@ +# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/helpers/fund-wallet/AGENTS.md b/packages/helpers/fund-wallet/AGENTS.md index dce476ba6..dcea67c80 100644 --- a/packages/helpers/fund-wallet/AGENTS.md +++ b/packages/helpers/fund-wallet/AGENTS.md @@ -1,104 +1,10 @@ -# CLAUDE.md — @bsv/fund-wallet +# ts-stack agent instructions -## Purpose (1-2 sentences) +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -Command-line faucet/funding tool for development and testing. Funds a remote wallet with satoshis from a local Metanet Desktop wallet (or any BRC-100 source) via private key derivation. - -## Public API surface - -### CLI Entry Point -Run as CLI-only; no programmatic TypeScript API exported. Executable: `fund-metanet` (for example, `npx --package @bsv/fund-wallet fund-metanet`). - -### Command-Line Flags -- `--chain ` — Required: 'test' or 'main' -- `--private-key ` — Required: private key in hexadecimal -- `--storage-url ` — Optional: remote wallet storage URL (default: `https://store-us-1.bsvb.tech`) -- `--satoshis ` — Optional: satoshis to fund. Omit to check balance only -- `--help` — Display usage - -### Behavior -1. Connects to remote storage URL (read-only for balance check) -2. Displays current wallet balance -3. If `--satoshis` provided: connects to local Metanet Desktop wallet, derives keys, builds transaction, sends funds -4. Prints transaction ID and WhatsOnChain link on success - -## Real usage patterns - -```bash -# Check balance only -npx --package @bsv/fund-wallet fund-metanet \ - --chain main \ - --private-key 0123456789abcdef... - -# Fund with 10,000 satoshis -npx --package @bsv/fund-wallet fund-metanet \ - --chain test \ - --private-key \ - --satoshis 10000 - -# Custom storage provider -npx --package @bsv/fund-wallet fund-metanet \ - --chain main \ - --private-key \ - --storage-url https://custom-store.example.com \ - --satoshis 5000 - -# Interactive mode (no args) -npx --package @bsv/fund-wallet fund-metanet -# Prompts: chain? storage URL? private key? satoshis? -``` - -## Key concepts - -- **Metanet Desktop** — Local BRC-100 wallet application; must be running to send funds -- **Remote Wallet** — The destination wallet at `--storage-url` -- **Balance Check** — Read-only; queries remote storage without signing -- **Key Derivation** — Derives identity key from private key via `@bsv/sdk` -- **Transaction Internalization** — Remote wallet internalizes the BEEF transaction into its own baskets -- **Test vs Main** — Argument determines which network is used; affects key derivation - -## Dependencies - -**Runtime:** -- `@bsv/sdk` ^2.1.6 peer (private keys and transaction construction) -- `@bsv/wallet-toolbox` workspace-compatible release (wallet and storage integration) -- `chalk` ^5.6.2 (colored CLI output) -- Node.js built-ins `node:crypto` and `node:readline` - -**Dev:** -- TypeScript, Vitest with V8 coverage, oxlint, tsdown, @types/node - -## Common pitfalls / gotchas - -1. **Metanet Desktop not running** — If `--satoshis` is provided but Metanet Desktop is not running/installed, tool fails with "not installed or not running" -2. **Private key format** — Must be exactly 64 hex characters and valid secp256k1 key material -3. **Network mismatch** — If you specify `--chain test` but try to connect to main network storage, balance will be 0 -4. **Storage URL validation** — Must be credential-free HTTPS; HTTP and embedded credentials are rejected -5. **Balance fetch only** — No Metanet Desktop needed if you omit `--satoshis` -6. **No signature verification** — Tool assumes storage URL is trustworthy; no BEEF validation on receive -7. **Numeric parsing** — Funding amounts must be non-negative safe integers - -## Spec conformance - -- **BRC-100** — Wallet interface (Metanet Desktop provider) -- **BRC-29** — Key derivation (identity key from private key) -- **BEEF** — Broadcast-Everything-BEEF transaction format -- **BSV Testnet/Mainnet** — Network selection via `--chain` flag - -## File map - -``` -fund-wallet/ - src/ - index.ts # Executable entrypoint - cli.ts # Parsing, validation, prompts, and funding flow - index.test.ts # Unit and wallet-contract coverage - dist/ - index.mjs # Compiled CLI (executable via bin.fund-metanet) -``` - -## Integration points - -- **Depends on:** `@bsv/sdk` (PrivateKey, Transaction), `@bsv/wallet-toolbox` (ServerWallet) -- **Used by:** Developers/testers needing to fund wallets during development, faucet operators -- **Complements:** `@bsv/amountinator` (could enhance output with currency conversion), any wallet that needs seeding +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/helpers/simple/.github/ISSUE_TEMPLATE/bug_report.md b/packages/helpers/simple/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 4fc56f13f..000000000 --- a/packages/helpers/simple/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: Bug Report -about: Report a bug or an issue you've found with `@bsv/sdk`. -title: "[BUG]" -labels: bug -assignees: '' - ---- - -## Bug Description - -Briefly describe the bug/issue you've encountered. - -## Steps to Reproduce - -1. Step 1 -2. Step 2 -3. ... - -## Expected Behavior - -What should have happened if the bug hadn't occurred? - -## Actual Behavior - -What actually happened? - -## Stack Traces or Screenshots - -If applicable, add screenshots or stack traces to help explain the issue. - -## Environment - -- OS: [e.g. MacOS, Windows] -- Node version: [e.g. 12.20] -- `@bsv/sdk` version: [e.g. 1.2.3] - -## Additional Information - -Provide any additional context or information about the bug. diff --git a/packages/helpers/simple/.github/ISSUE_TEMPLATE/discussion.md b/packages/helpers/simple/.github/ISSUE_TEMPLATE/discussion.md deleted file mode 100644 index 52aa083ce..000000000 --- a/packages/helpers/simple/.github/ISSUE_TEMPLATE/discussion.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Discussion -about: Propose a discussion or seek clarification about a feature or topic. -title: "[DISCUSSION]" -labels: discussion -assignees: '' - ---- - -## Summary - -Briefly describe the topic you'd like to discuss. - -## Motivation - -Why do you believe this to be important? - -## Description - -Provide a detailed description or elaborate on your topic. - -## Additional References - -Provide any additional articles, links, or context that would help facilitate the discussion. diff --git a/packages/helpers/simple/.github/pull_request_template.md b/packages/helpers/simple/.github/pull_request_template.md deleted file mode 100644 index c6182f7f8..000000000 --- a/packages/helpers/simple/.github/pull_request_template.md +++ /dev/null @@ -1,25 +0,0 @@ -## Description of Changes - -Provide a brief description of the changes you've made. - -## Linked Issues / Tickets - -Reference any related issues or tickets, e.g. "Closes #123". - -## Testing Procedure - -Describe the tests you've added or any testing steps you've taken. - -- [ ] I have added new unit tests -- [ ] All tests pass locally -- [ ] I have tested manually in my local environment - -## Checklist - -- [ ] I have performed a self-review of my own code -- [ ] I have made corresponding changes to the documentation -- [ ] My changes generate no new warnings -- [ ] I have updated `CHANGELOG.md` with my changes -- [ ] I have run `npm run doc` and `npm run lint` one final time before requesting a review -- [ ] I have fixed all linter errors to ensure these changes are compliant with `ts-standard` -- [ ] I have run `npm version patch` so that my changes will trigger a new version to be released when they are merged diff --git a/packages/helpers/simple/.github/workflows/push.yaml b/packages/helpers/simple/.github/workflows/push.yaml deleted file mode 100644 index a7904ddf2..000000000 --- a/packages/helpers/simple/.github/workflows/push.yaml +++ /dev/null @@ -1,77 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - main - tags: - - 'v*.*.*' - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - continue-on-error: true - with: - use_oidc: true - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/helpers/simple/AGENTS.md b/packages/helpers/simple/AGENTS.md index 7ced47770..dcea67c80 100644 --- a/packages/helpers/simple/AGENTS.md +++ b/packages/helpers/simple/AGENTS.md @@ -1,1013 +1,10 @@ -# @bsv/simple v2 — AI Knowledge Base +# ts-stack agent instructions -## 1. Library Architecture +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -**Package:** `@bsv/simple` v0.2.3 - -**Entry points:** -- `@bsv/simple` — Browser-safe exports only (no server utilities) -- `@bsv/simple/browser` — Browser-only: `createWallet()`, `Wallet`, `Overlay`, `Certifier`, `DID`, `CredentialSchema`, `CredentialIssuer`, `MemoryRevocationStore` -- `@bsv/simple/server` — Server-only: `ServerWallet`, `FileRevocationStore`, `generatePrivateKey()`, handler factories (`createIdentityRegistryHandler`, `createDIDResolverHandler`, `createServerWalletHandler`, `createCredentialIssuerHandler`), utility classes (`JsonFileStore`, `IdentityRegistry`, `DIDResolverService`, `ServerWalletManager`) - -**Module composition pattern:** `WalletCore` (abstract base) defines shared methods. `_BrowserWallet` / `_ServerWallet` extend it. Factory functions (`createWallet`, `ServerWallet.create`) instantiate the class then `Object.assign()` mixin methods from each module. The composed type is a union: `_BrowserWallet & TokenMethods & InscriptionMethods & MessageBoxMethods & CertificationMethods & OverlayMethods & DIDMethods & CredentialMethods`. - -**Build:** `npm run build` (runs `tsc`) - -**Dependencies:** -- `@bsv/sdk` ^2.0.4 — Core BSV blockchain SDK -- `@bsv/wallet-toolbox` ^2.0.19 — Backend wallet (Node.js only, used by ServerWallet) -- `@bsv/wallet-toolbox-client` ^2.0.19 — Wallet toolbox client -- `@bsv/message-box-client` ^2.0.2 — PeerPayClient for P2P messaging - -**Source layout:** -``` -src/ -├── core/ -│ ├── WalletCore.ts — Abstract base: wallet info, key derivation, pay, send, fundServerWallet -│ ├── types.ts — All shared TypeScript interfaces (including server handler config types) -│ ├── errors.ts — Error classes: SimpleError, WalletError, TransactionError, MessageBoxError, CertificationError, DIDError, CredentialError -│ └── defaults.ts — DEFAULT_CONFIG, mergeDefaults() -├── modules/ -│ ├── tokens.ts — createTokenMethods(): createToken, listTokenDetails, sendToken, redeemToken, sendTokenViaMessageBox, listIncomingTokens, acceptIncomingToken -│ ├── inscriptions.ts — createInscriptionMethods(): inscribeText, inscribeJSON, inscribeFileHash, inscribeImageHash -│ ├── messagebox.ts — createMessageBoxMethods(): certifyForMessageBox, getMessageBoxHandle, revokeMessageBoxCertification, sendMessageBoxPayment, listIncomingPayments, acceptIncomingPayment, registerIdentityTag, lookupIdentityByTag, listMyTags, revokeIdentityTag -│ ├── certification.ts — Certifier class + createCertificationMethods(): acquireCertificateFrom, listCertificatesFrom, relinquishCert -│ ├── overlay.ts — Overlay class + createOverlayMethods(): advertiseSHIP, advertiseSLAP, broadcastAction, withRetry -│ ├── did.ts — DID class + createDIDMethods(): getDID, resolveDID, registerDID -│ ├── credentials.ts — CredentialSchema, CredentialIssuer, MemoryRevocationStore, toVerifiableCredential, toVerifiablePresentation + createCredentialMethods(): acquireCredential, listCredentials, createPresentation -│ └── file-revocation-store.ts — FileRevocationStore (Node.js only, uses fs) -├── server/ -│ ├── handler-types.ts — HandlerRequest, HandlerResponse, toNextHandlers() (framework-agnostic) -│ ├── json-file-store.ts — JsonFileStore generic file persistence -│ ├── identity-registry.ts — IdentityRegistry class + createIdentityRegistryHandler() -│ ├── did-resolver.ts — DIDResolverService class + createDIDResolverHandler() -│ ├── server-wallet-manager.ts — ServerWalletManager class + createServerWalletHandler() -│ ├── credential-issuer-handler.ts — createCredentialIssuerHandler() -│ └── index.ts — Re-exports all server/ utilities -├── browser.ts — BrowserWallet type + createWallet() factory + re-exports -├── server.ts — ServerWallet type + ServerWallet.create() factory + generatePrivateKey() + re-exports from server/ + re-exports from browser -└── index.ts — Browser-safe exports only (no server utilities) -``` - ---- - -## 2. Critical Gotchas - -1. **`basket insertion` vs `wallet payment` are MUTUALLY EXCLUSIVE** in `internalizeAction`. You cannot use both on the same output. `wallet payment` provides derivation info for spending (output NOT in any app basket). `basket insertion` puts output in a named basket (derivation info goes in `customInstructions`). - -2. **PeerPayClient.acceptPayment() swallows errors** — Returns the string `'Unable to receive payment!'` instead of throwing. Always check: `if (typeof result === 'string') throw new Error(result)`. - -3. **`result.tx` from `createAction` may be `undefined`** — Always check before using for overlay broadcasting. - -4. **BRC-29 Payment Derivation Protocol ID:** `[2, '3241645161d8']` - -5. **FileRevocationStore is server-only** — It's in a separate file (`file-revocation-store.ts`) to avoid bundling Node.js `fs` in browser builds. Import from `@bsv/simple/server`. - -6. **Overlay topics must start with `tm_`**, lookup services must start with `ls_`** — The Overlay class enforces these prefixes and throws if violated. - -7. **Token send/redeem uses signableTransaction flow** — These operations require a two-step `createAction` → `signAction` pattern with PushDrop unlock templates. - -8. **PeerPayClient instance is lazily created and reused** — The messagebox module creates one `PeerPayClient` per wallet instance and reuses it across calls. - -9. **`pay()` uses PeerPayClient.sendPayment()** — Payments are routed via MessageBox P2P, not direct on-chain P2PKH. For direct on-chain payments, use `send()` with a P2PKH output. - -10. **Server exports not available from `@bsv/simple`** — Server-only utilities (ServerWallet, handler factories, FileRevocationStore, generatePrivateKey) must be imported from `@bsv/simple/server`. - ---- - -## 3. Browser Wallet API - -### Initialization - -```typescript -import { createWallet } from '@bsv/simple/browser' - -const wallet = await createWallet() -// Optional: pass defaults -const wallet = await createWallet({ network: 'main' }) -``` - -### Wallet Info (WalletCore) - -| Method | Returns | Description | -|--------|---------|-------------| -| `getIdentityKey()` | `string` | Compressed public key hex (66 chars) | -| `getAddress()` | `string` | P2PKH address derived from identity key | -| `getStatus()` | `WalletStatus` | `{ isConnected, identityKey, network }` | -| `getWalletInfo()` | `WalletInfo` | `{ identityKey, address, network, isConnected }` | -| `getClient()` | `WalletInterface` | Underlying SDK wallet client | -| `getBalance(basket?)` | `Promise` | Wallet balance (optimized via specOp when no basket) | - -### Balance (WalletCore) - -| Method | Params | Returns | Description | -|--------|--------|---------|-------------| -| `getBalance(basket?)` | `string?` | `Promise` | Get wallet balance. Without basket: uses wallet-toolbox `specOpWalletBalance` for optimized total balance query. With basket: iterates outputs to calculate total and spendable satoshis. | - -```typescript -interface BalanceResult { - totalSatoshis: number // sum of all output satoshis - totalOutputs: number // count of outputs - spendableSatoshis: number // sum of spendable output satoshis - spendableOutputs: number // count of spendable outputs -} -``` - -### Key Derivation (WalletCore) - -| Method | Params | Returns | Description | -|--------|--------|---------|-------------| -| `derivePublicKey(protocolID, keyID, counterparty?, forSelf?)` | `[SecurityLevel, string], string, string?, boolean?` | `Promise` | Derive public key for any protocol | -| `derivePaymentKey(counterparty, invoiceNumber?)` | `string, string?` | `Promise` | Derive BRC-29 payment key | - -### Payments (WalletCore) - -| Method | Params | Returns | Description | -|--------|--------|---------|-------------| -| `pay(options)` | `PaymentOptions` | `Promise` | Payment via MessageBox P2P (PeerPayClient) | -| `send(options)` | `SendOptions` | `Promise` | Multi-output: P2PKH + OP_RETURN + PushDrop in one tx | -| `fundServerWallet(request, basket?)` | `PaymentRequest, string?` | `Promise` | Fund a ServerWallet using BRC-29 derivation (legacy) | - -### Direct Payments — BRC-29 Wallet Payment Internalization (WalletCore) - -| Method | Params | Returns | Description | -|--------|--------|---------|-------------| -| `createPaymentRequest(options)` | `{ satoshis, memo? }` | `PaymentRequest` | Generate BRC-29 derivation data for someone to pay you | -| `sendDirectPayment(request)` | `PaymentRequest` | `Promise` | Create BRC-29 derived P2PKH tx + return remittance data | -| `receiveDirectPayment(payment)` | `IncomingPayment` | `Promise` | Internalize into wallet balance via `wallet payment` (NOT into a basket) | - -### Tokens (tokens module) - -| Method | Params | Returns | Description | -|--------|--------|---------|-------------| -| `createToken(options)` | `TokenOptions` | `Promise` | Create encrypted PushDrop token | -| `listTokenDetails(basket?)` | `string?` | `Promise` | List + decrypt tokens in a basket | -| `sendToken(options)` | `SendTokenOptions` | `Promise` | Transfer token to another key (on-chain) | -| `redeemToken(options)` | `RedeemTokenOptions` | `Promise` | Spend/destroy a token | -| `sendTokenViaMessageBox(options)` | `SendTokenOptions` | `Promise` | Transfer token via MessageBox P2P | -| `listIncomingTokens()` | — | `Promise` | List tokens waiting in MessageBox inbox | -| `acceptIncomingToken(token, basket?)` | `any, string?` | `Promise` | Accept incoming token into a basket | - -### Inscriptions (inscriptions module) - -| Method | Params | Returns | Description | -|--------|--------|---------|-------------| -| `inscribeText(text, opts?)` | `string, { basket?, description? }?` | `Promise` | OP_RETURN text inscription | -| `inscribeJSON(data, opts?)` | `object, { basket?, description? }?` | `Promise` | OP_RETURN JSON inscription | -| `inscribeFileHash(hash, opts?)` | `string, { basket?, description? }?` | `Promise` | OP_RETURN SHA-256 file hash | -| `inscribeImageHash(hash, opts?)` | `string, { basket?, description? }?` | `Promise` | OP_RETURN SHA-256 image hash | - -### MessageBox (messagebox module) - -| Method | Params | Returns | Description | -|--------|--------|---------|-------------| -| `certifyForMessageBox(handle, registryUrl?, host?)` | `string, string?, string?` | `Promise<{ txid, handle }>` | Register handle + anoint MessageBox host | -| `getMessageBoxHandle(registryUrl?)` | `string?` | `Promise` | Check if wallet has a registered handle | -| `revokeMessageBoxCertification(registryUrl?)` | `string?` | `Promise` | Remove all registered handles | -| `sendMessageBoxPayment(to, satoshis)` | `string, number` | `Promise` | Send payment via MessageBox P2P | -| `listIncomingPayments()` | — | `Promise` | List payments in MessageBox inbox | -| `acceptIncomingPayment(payment, basket?)` | `any, string?` | `Promise` | Accept payment (into basket or via PeerPay) | -| `registerIdentityTag(tag, registryUrl?)` | `string, string?` | `Promise<{ tag }>` | Register an identity tag | -| `lookupIdentityByTag(query, registryUrl?)` | `string, string?` | `Promise<{ tag, identityKey }[]>` | Search identity registry | -| `listMyTags(registryUrl?)` | `string?` | `Promise<{ tag, createdAt }[]>` | List own registered tags | -| `revokeIdentityTag(tag, registryUrl?)` | `string, string?` | `Promise` | Remove a registered tag | - -### Certification (certification module) - -| Method | Params | Returns | Description | -|--------|--------|---------|-------------| -| `acquireCertificateFrom(config)` | `{ serverUrl, replaceExisting? }` | `Promise` | Acquire certificate from remote server (uses `?action=info` and `?action=certify` query params) | -| `listCertificatesFrom(config)` | `{ certifiers, types, limit? }` | `Promise<{ totalCertificates, certificates }>` | List certificates by certifier/type | -| `relinquishCert(args)` | `{ type, serialNumber, certifier }` | `Promise` | Revoke/relinquish a certificate | - -### DID (did module) - -| Method | Params | Returns | Description | -|--------|--------|---------|-------------| -| `getDID()` | — | `DIDDocument` | Get this wallet's DID Document | -| `resolveDID(didString)` | `string` | `DIDDocument` | Resolve any `did:bsv:` to its DID Document | -| `registerDID(options?)` | `{ persist? }?` | `Promise` | Persist DID as a BSV certificate | - -### Credentials (credentials module) - -| Method | Params | Returns | Description | -|--------|--------|---------|-------------| -| `acquireCredential(config)` | `{ serverUrl, schemaId?, fields?, replaceExisting? }` | `Promise` | Acquire VC from remote issuer | -| `listCredentials(config)` | `{ certifiers, types, limit? }` | `Promise` | List wallet certs as W3C VCs | -| `createPresentation(credentials)` | `VerifiableCredential[]` | `VerifiablePresentation` | Wrap VCs into a VP | - ---- - -## 4. Server Wallet API - -### Initialization - -```typescript -// In a Next.js API route (server-only): -const { ServerWallet } = await import('@bsv/simple/server') - -const wallet = await ServerWallet.create({ - privateKey: 'hex_private_key', - network: 'main', // optional, default 'main' - storageUrl: 'https://storage.babbage.systems' // optional -}) -``` - -### ServerWallet-specific Methods - -| Method | Params | Returns | Description | -|--------|--------|---------|-------------| -| `receivePayment(payment)` | `IncomingPayment` | `Promise` | **Deprecated.** Use `receiveDirectPayment()` (inherited from WalletCore). Kept for backward compat with `server_funding` label. | - -### Shared Methods - -ServerWallet has all the same methods as BrowserWallet (pay, send, createToken, inscribeText, createPaymentRequest, sendDirectPayment, receiveDirectPayment, etc.) via the same module composition pattern. - ---- - -## 5. Standalone Classes - -### DID - -```typescript -import { DID } from '@bsv/simple/browser' - -DID.fromIdentityKey('02abc...') // → DIDDocument -DID.parse('did:bsv:02abc...') // → { method: 'bsv', identityKey: '02abc...' } -DID.isValid('did:bsv:02abc...') // → boolean -DID.getCertificateType() // → base64 string for 'did:bsv' -``` - -### Certifier - -```typescript -import { Certifier } from '@bsv/simple/browser' - -const certifier = await Certifier.create() // random key -const certifier = await Certifier.create({ privateKey: 'hex' }) // specific key -const certifier = await Certifier.create({ - privateKey: 'hex', - certificateType: 'base64type', - defaultFields: { role: 'admin' }, - includeTimestamp: true // default: true -}) - -certifier.getInfo() // → { publicKey, certificateType } -await certifier.certify(wallet, { extra: 'field' }) // → CertificateData (also acquires into wallet) -``` - -### CredentialSchema - -```typescript -import { CredentialSchema } from '@bsv/simple/browser' - -const schema = new CredentialSchema({ - id: 'my-schema', - name: 'My Credential', - description: 'Optional description', - fields: [ - { key: 'name', label: 'Full Name', type: 'text', required: true }, - { key: 'email', label: 'Email', type: 'email', required: true }, - { key: 'role', label: 'Role', type: 'select', options: [{ value: 'admin', label: 'Admin' }] } - ], - validate: (values) => values.name.length < 2 ? 'Name too short' : null, - computedFields: (values) => ({ verified: 'true', timestamp: Date.now().toString() }) -}) - -schema.validate({ name: 'A', email: 'test@test.com' }) // → 'Name too short' -schema.computeFields({ name: 'Alice' }) // → { name: 'Alice', verified: 'true', ... } -schema.getInfo() // → { id, name, description, certificateTypeBase64, fieldCount } -``` - -### CredentialIssuer - -```typescript -import { CredentialIssuer } from '@bsv/simple/browser' - -const issuer = await CredentialIssuer.create({ - privateKey: 'hex_key', - schemas: [schemaConfig], // CredentialSchemaConfig[] - revocation: { - enabled: true, - wallet: serverWalletInstance, // WalletInterface for creating revocation UTXOs - store: new MemoryRevocationStore() // or FileRevocationStore for server - } -}) - -const vc = await issuer.issue(subjectIdentityKey, 'schema-id', { name: 'Alice' }) -const result = await issuer.verify(vc) // → { valid, revoked, errors, issuer, subject, type } -await issuer.revoke(serialNumber) // → { txid } -await issuer.isRevoked(serialNumber) // → boolean -issuer.getInfo() // → { publicKey, did, schemas: [{ id, name }] } -``` - -### MemoryRevocationStore / FileRevocationStore - -```typescript -import { MemoryRevocationStore } from '@bsv/simple/browser' -import { FileRevocationStore } from '@bsv/simple/server' - -// Memory (browser/tests) -const store = new MemoryRevocationStore() - -// File (server — writes .revocation-secrets.json) -const store = new FileRevocationStore() // default path -const store = new FileRevocationStore('/path/to/secrets.json') // custom path - -// Both implement RevocationStore interface: -await store.save(serialNumber, { secret, outpoint, beef }) -await store.load(serialNumber) // → RevocationRecord | undefined -await store.delete(serialNumber) -await store.has(serialNumber) // → boolean -await store.findByOutpoint(outpoint) // → boolean -``` - -### Overlay - -```typescript -import { Overlay } from '@bsv/simple/browser' - -const overlay = await Overlay.create({ - topics: ['tm_my_topic'], - network: 'mainnet', // 'mainnet' | 'testnet' | 'local' - slapTrackers: ['https://...'], // optional - hostOverrides: { tm_topic: ['url'] }, // optional - additionalHosts: { tm_topic: ['url'] } // optional -}) - -overlay.getInfo() // → { topics, network } -overlay.addTopic('tm_new') // add topic (must start with tm_) -overlay.removeTopic('tm_old') // remove topic -await overlay.broadcast(transaction) // → OverlayBroadcastResult -await overlay.broadcast(tx, ['tm_specific']) // broadcast to specific topics -await overlay.query('ls_service', queryData) // → LookupAnswer -await overlay.lookupOutputs('ls_service', q) // → OverlayOutput[] -overlay.getBroadcaster() // raw TopicBroadcaster -overlay.getResolver() // raw LookupResolver -``` - -### W3C VC/VP Utilities - -```typescript -import { toVerifiableCredential, toVerifiablePresentation } from '@bsv/simple/browser' - -const vc = toVerifiableCredential(certData, issuerPublicKey, { credentialType: 'MyCredential' }) -const vp = toVerifiablePresentation([vc1, vc2], holderPublicKey) -``` - ---- - -## 6. Server Handler Factories - -Pre-built Next.js API route handlers that eliminate boilerplate. Each factory returns `{ GET, POST }` compatible with Next.js App Router. No `@bsv/sdk` import needed in consumer code. - -### generatePrivateKey() - -```typescript -import { generatePrivateKey } from '@bsv/simple/server' -const key = generatePrivateKey() // random hex private key, no @bsv/sdk needed -``` - -### JsonFileStore\ - -Generic file-based JSON persistence used by all handlers internally. Also available for custom use. - -```typescript -import { JsonFileStore } from '@bsv/simple/server' -const store = new JsonFileStore<{ name: string }>('/path/to/data.json') -store.save({ name: 'Alice' }) -const data = store.load() // { name: 'Alice' } | null -store.exists() // boolean -store.delete() -``` - -### createIdentityRegistryHandler(config?) - -Identity tag/handle registry for MessageBox. Replaces ~150 lines of route code with 3 lines. - -```typescript -// app/api/identity-registry/route.ts -import { createIdentityRegistryHandler } from '@bsv/simple/server' -const handler = createIdentityRegistryHandler() -export const GET = handler.GET, POST = handler.POST -``` - -**Config options:** -```typescript -interface IdentityRegistryConfig { - store?: IdentityRegistryStore // Custom backend (default: file-based .identity-registry.json) - validateTag?: (tag: string, identityKey: string) => string | null // Custom validation - maxTagsPerIdentity?: number // Default: Infinity -} -``` - -**API contract** (matches what `messagebox.ts` already calls): -- `GET ?action=lookup&query=...` → `{ success, results: [{ tag, identityKey }] }` -- `GET ?action=list&identityKey=...` → `{ success, tags: [{ tag, createdAt }] }` -- `POST ?action=register` body: `{ tag, identityKey }` → `{ success, message, tag }` -- `POST ?action=revoke` body: `{ tag, identityKey }` → `{ success, message, tag }` - -### createDIDResolverHandler(config?) - -Server-side DID resolution proxy. Replaces ~322 lines of OP_RETURN parsing + WoC chain-following. - -```typescript -// app/api/resolve-did/route.ts -import { createDIDResolverHandler } from '@bsv/simple/server' -const handler = createDIDResolverHandler() -export const GET = handler.GET -``` - -**Config options:** -```typescript -interface DIDResolverConfig { - resolverUrl?: string // Default: nChain Universal Resolver - wocBaseUrl?: string // Default: WoC mainnet API - resolverTimeout?: number // Default: 10000ms - maxHops?: number // Default: 100 -} -``` - -**API:** `GET ?did=did:bsv:` → `DIDResolutionResult` - -### createServerWalletHandler(config?) - -Server wallet with lazy-init singleton + key persistence. Replaces ~105 lines of boilerplate. - -```typescript -// app/api/server-wallet/route.ts -import { createServerWalletHandler } from '@bsv/simple/server' -const handler = createServerWalletHandler() -export const GET = handler.GET, POST = handler.POST -``` - -**Config options:** -```typescript -interface ServerWalletManagerConfig { - envVar?: string // Default: 'SERVER_PRIVATE_KEY' - keyFile?: string // Default: '.server-wallet.json' in cwd - network?: 'main' | 'testnet' - storageUrl?: string // Default: 'https://storage.babbage.systems' - defaultRequestSatoshis?: number // Default: 1000 - requestMemo?: string -} -``` - -**API:** -- `GET ?action=create|status|request|balance|outputs|reset` -- `POST ?action=receive` body: `{ tx, senderIdentityKey, derivationPrefix, derivationSuffix, outputIndex }` - -### createCredentialIssuerHandler(config) - -W3C Verifiable Credential issuer. Replaces ~220 lines + `[[...path]]` catch-all route. Uses a normal `route.ts`. - -```typescript -// app/api/credential-issuer/route.ts (no [[...path]] needed!) -import { createCredentialIssuerHandler } from '@bsv/simple/server' -const handler = createCredentialIssuerHandler({ - schemas: [{ - id: 'freelancer-verified', - name: 'VerifiedFreelancer', - fields: [ - { key: 'name', label: 'Full Name', type: 'text', required: true }, - { key: 'skill', label: 'Primary Skill', type: 'select', required: true }, - { key: 'rate', label: 'Hourly Rate', type: 'number', required: true }, - ] - }] -}) -export const GET = handler.GET, POST = handler.POST -``` - -**Config options:** -```typescript -interface CredentialIssuerHandlerConfig { - schemas: CredentialSchemaConfig[] // Required: at least one schema - envVar?: string // Default: 'CREDENTIAL_ISSUER_KEY' - keyFile?: string // Default: '.credential-issuer-key.json' - serverWalletManager?: ServerWalletManager // For revocation UTXOs - revocationStorePath?: string -} -``` - -**API (query-param based):** -- `GET ?action=info` → `{ certifierPublicKey, certificateType, schemas }` -- `GET ?action=schema&id=...` → schema details -- `GET ?action=status&serialNumber=...` → revocation status -- `POST ?action=certify` body: `{ identityKey, schemaId, fields }` → `CertificateData` -- `POST ?action=issue` body: `{ subjectKey, schemaId, fields }` → `{ credential: VerifiableCredential }` -- `POST ?action=verify` body: `{ credential }` → `{ verification: VerificationResult }` -- `POST ?action=revoke` body: `{ serialNumber }` → `{ txid }` - -Also supports legacy path-based `/api/info` and `/api/certify` for backward compatibility. - -### IdentityRegistry (core class) - -Framework-agnostic registry logic, usable outside Next.js: - -```typescript -import { IdentityRegistry } from '@bsv/simple/server' -const registry = new IdentityRegistry() -registry.register('alice', identityKey) -registry.lookup('ali') // [{ tag: 'alice', identityKey: '...' }] -registry.list(identityKey) // [{ tag: 'alice', createdAt: '...' }] -registry.revoke('alice', identityKey) -``` - -### DIDResolverService (core class) - -```typescript -import { DIDResolverService } from '@bsv/simple/server' -const resolver = new DIDResolverService() -const result = await resolver.resolve('did:bsv:') -``` - -### ServerWalletManager (core class) - -```typescript -import { ServerWalletManager } from '@bsv/simple/server' -const manager = new ServerWalletManager() -const wallet = await manager.getWallet() // lazy init + key persist -manager.getStatus() // { saved: boolean, identityKey: string | null } -manager.reset() -``` - ---- - -## 7. Next.js Integration Guide - -### Import Patterns - -```typescript -// Browser components (client-side): -'use client' -import { createWallet, Certifier, DID, Overlay } from '@bsv/simple/browser' -import { CredentialSchema, CredentialIssuer, MemoryRevocationStore } from '@bsv/simple/browser' - -// Server API routes (handler factories — preferred): -import { createIdentityRegistryHandler } from '@bsv/simple/server' -import { createDIDResolverHandler } from '@bsv/simple/server' -import { createServerWalletHandler } from '@bsv/simple/server' -import { createCredentialIssuerHandler } from '@bsv/simple/server' - -// Server utilities (when you need lower-level access): -const { ServerWallet, generatePrivateKey } = await import('@bsv/simple/server') -const { FileRevocationStore } = await import('@bsv/simple/server') -``` - -### next.config.ts (CRITICAL for Turbopack) - -```typescript -import type { NextConfig } from "next"; - -const nextConfig: NextConfig = { - serverExternalPackages: [ - "@bsv/wallet-toolbox", - "knex", - "better-sqlite3", - "tedious", - "mysql", - "mysql2", - "pg", - "pg-query-stream", - "oracledb", - "dotenv" - ] -}; - -export default nextConfig; -``` - -Without `serverExternalPackages`, Turbopack will try to bundle `@bsv/wallet-toolbox` and its database drivers for the browser, causing build failures. - -### Browser Wallet Setup - -```typescript -'use client' -import { useState, useEffect } from 'react' -import { createWallet, type BrowserWallet } from '@bsv/simple/browser' - -export default function Page() { - const [wallet, setWallet] = useState(null) - - const connect = async () => { - const w = await createWallet() - setWallet(w) - } - - return -} -``` - -### Server API Routes (Simplified) - -All server routes use handler factories — no boilerplate needed: - -```typescript -// app/api/identity-registry/route.ts -import { createIdentityRegistryHandler } from '@bsv/simple/server' -const handler = createIdentityRegistryHandler() -export const GET = handler.GET, POST = handler.POST - -// app/api/resolve-did/route.ts -import { createDIDResolverHandler } from '@bsv/simple/server' -const handler = createDIDResolverHandler() -export const GET = handler.GET - -// app/api/server-wallet/route.ts -import { createServerWalletHandler } from '@bsv/simple/server' -const handler = createServerWalletHandler() -export const GET = handler.GET, POST = handler.POST - -// app/api/credential-issuer/route.ts (no [[...path]] needed!) -import { createCredentialIssuerHandler } from '@bsv/simple/server' -const handler = createCredentialIssuerHandler({ - schemas: [{ id: 'my-credential', name: 'MyCredential', fields: [...] }] -}) -export const GET = handler.GET, POST = handler.POST -``` - -### Key Persistence Pattern - -Server wallet private keys persist automatically via `ServerWalletManager`: -1. Check `process.env.SERVER_PRIVATE_KEY` -2. If not set, check `.server-wallet.json` file -3. If not found, generate key via `generatePrivateKey()` and save to file - -No `@bsv/sdk` import needed — use `generatePrivateKey()` from `@bsv/simple/server`. - ---- - -## 8. Code Recipes - -### 7.0 Check Wallet Balance - -```typescript -// Overall wallet balance (optimized — no output iteration) -const balance = await wallet.getBalance() -console.log(balance.totalSatoshis) // total spendable sats - -// Per-basket balance -const tokenBalance = await wallet.getBalance('tokens') -console.log(tokenBalance.spendableSatoshis) // spendable sats in basket -console.log(tokenBalance.spendableOutputs) // spendable output count -console.log(tokenBalance.totalSatoshis) // total sats (incl. non-spendable) -console.log(tokenBalance.totalOutputs) // total output count -``` - -### 7.1 Connect Wallet + Auto-check MessageBox Handle - -```typescript -const wallet = await createWallet() -const handle = await wallet.getMessageBoxHandle('/api/identity-registry') -if (handle) { - console.log('MessageBox handle:', handle) -} else { - await wallet.certifyForMessageBox('@alice', '/api/identity-registry') -} -``` - -### 7.2 Simple Payment via MessageBox P2P - -```typescript -const result = await wallet.pay({ - to: recipientKey, - satoshis: 1000 -}) -console.log('TXID:', result.txid) -``` - -### 7.3 Multi-Output Send (P2PKH + OP_RETURN + PushDrop) - -```typescript -const result = await wallet.send({ - outputs: [ - { to: recipientKey, satoshis: 1000, basket: 'payments' }, // P2PKH - { data: ['Hello blockchain!'], basket: 'text' }, // OP_RETURN - { to: wallet.getIdentityKey(), data: [{ value: 42 }], satoshis: 1, basket: 'tokens' } // PushDrop - ], - description: 'Multi-output transaction' -}) -// result.outputDetails: [{ index: 0, type: 'p2pkh' }, { index: 1, type: 'op_return' }, { index: 2, type: 'pushdrop' }] -``` - -### 7.4 Create / List / Send / Redeem Tokens - -```typescript -// Create -const token = await wallet.createToken({ - data: { type: 'loyalty', points: 100 }, - basket: 'my-tokens', - satoshis: 1 -}) - -// List with decryption -const tokens = await wallet.listTokenDetails('my-tokens') -// [{ outpoint, satoshis, data: { type: 'loyalty', points: 100 }, protocolID, keyID, counterparty }] - -// Send to another key -await wallet.sendToken({ basket: 'my-tokens', outpoint: tokens[0].outpoint, to: recipientKey }) - -// Redeem (destroy) -await wallet.redeemToken({ basket: 'my-tokens', outpoint: tokens[0].outpoint }) -``` - -### 7.5 Token Transfer via MessageBox - -```typescript -// Sender -await wallet.sendTokenViaMessageBox({ basket: 'my-tokens', outpoint: '...', to: recipientKey }) - -// Recipient -const incoming = await wallet.listIncomingTokens() -await wallet.acceptIncomingToken(incoming[0], 'received-tokens') -``` - -### 7.6 Text / JSON Inscriptions - -```typescript -const text = await wallet.inscribeText('Hello blockchain!') -// { txid, type: 'text', dataSize: 17, basket: 'text' } - -const json = await wallet.inscribeJSON({ title: 'Document', created: Date.now() }) -// { txid, type: 'json', dataSize: ..., basket: 'json' } - -const fileHash = await wallet.inscribeFileHash('a'.repeat(64)) -// { txid, type: 'file-hash', dataSize: 64, basket: 'hash-document' } -``` - -### 7.7 MessageBox: Certify, Send, Receive Payments - -```typescript -// Certify identity -await wallet.certifyForMessageBox('@alice', '/api/identity-registry') - -// Search for someone -const results = await wallet.lookupIdentityByTag('bob', '/api/identity-registry') - -// Send payment -await wallet.sendMessageBoxPayment(results[0].identityKey, 1000) - -// Receive payments -const incoming = await wallet.listIncomingPayments() -await wallet.acceptIncomingPayment(incoming[0], 'received-payments') -``` - -### 7.8 Direct Payments (BRC-29 Wallet Payment Internalization) - -```typescript -// Direct payments work on BOTH browser and server wallets. -// Funds go directly into the wallet's spendable balance (NOT into a basket). - -// --- Flow: Browser pays Server --- - -// Server: generate payment request -const request = serverWallet.createPaymentRequest({ satoshis: 2000 }) - -// Browser: create BRC-29 derived P2PKH transaction -const payment = await browserWallet.sendDirectPayment(request) - -// Browser: send tx + remittance to server (via API) -await fetch('/api/receive-payment', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - tx: Array.from(payment.tx), - senderIdentityKey: payment.senderIdentityKey, - derivationPrefix: payment.derivationPrefix, - derivationSuffix: payment.derivationSuffix, - outputIndex: payment.outputIndex - }) -}) - -// Server: internalize -await serverWallet.receiveDirectPayment({ tx, senderIdentityKey, derivationPrefix, derivationSuffix, outputIndex: 0 }) - -// --- Flow: Server pays Browser --- - -// Browser: create payment request -const request = browserWallet.createPaymentRequest({ satoshis: 100 }) -// ... send request to server via API ... - -// Server: create payment -const payment = await serverWallet.sendDirectPayment(request) -// ... return payment data to browser ... - -// Browser: internalize into wallet balance -await browserWallet.receiveDirectPayment({ - tx: paymentData.tx, - senderIdentityKey: paymentData.senderIdentityKey, - derivationPrefix: paymentData.derivationPrefix, - derivationSuffix: paymentData.derivationSuffix, - outputIndex: paymentData.outputIndex -}) -``` - -### 7.8b Server Wallet: Legacy Fund Flow - -```typescript -// Legacy pattern — prefer sendDirectPayment/receiveDirectPayment instead -const { ServerWallet } = await import('@bsv/simple/server') -const server = await ServerWallet.create({ privateKey: 'hex', network: 'main' }) -const request = server.createPaymentRequest({ satoshis: 50000 }) -const result = await wallet.fundServerWallet(request, 'server-funding') -await server.receivePayment({ tx, senderIdentityKey, derivationPrefix, derivationSuffix, outputIndex: 0 }) -``` - -### 7.9 DID: Get, Register, Resolve - -```typescript -import { DID } from '@bsv/simple/browser' - -// Get DID document for this wallet -const didDoc = wallet.getDID() -// { '@context': [...], id: 'did:bsv:02abc...', controller: '...', verificationMethod: [...] } - -// Register DID (persists as BSV certificate) -await wallet.registerDID() - -// Resolve any DID -const doc = wallet.resolveDID('did:bsv:02abc...') - -// Static utility -DID.isValid('did:bsv:02abc...') // true -DID.parse('did:bsv:02abc...') // { method: 'bsv', identityKey: '02abc...' } -``` - -### 7.10 Credentials: Issue VC, List VCs, Create Presentation - -```typescript -import { CredentialIssuer, CredentialSchema, MemoryRevocationStore } from '@bsv/simple/browser' - -// Define schema -const schema = new CredentialSchema({ - id: 'age-verification', - name: 'AgeVerification', - fields: [ - { key: 'name', label: 'Name', type: 'text', required: true }, - { key: 'over18', label: 'Over 18', type: 'checkbox', required: true } - ] -}) - -// Create issuer -const issuer = await CredentialIssuer.create({ - privateKey: 'hex_key', - schemas: [schema.getConfig()], - revocation: { enabled: false } -}) - -// Issue -const vc = await issuer.issue(subjectKey, 'age-verification', { name: 'Alice', over18: 'true' }) - -// List from wallet -const vcs = await wallet.listCredentials({ - certifiers: [issuer.getInfo().publicKey], - types: [schema.getInfo().certificateTypeBase64] -}) - -// Create presentation -const vp = wallet.createPresentation(vcs) -``` - -### 7.11 Overlay: Create, Query, Advertise SHIP/SLAP - -```typescript -import { Overlay } from '@bsv/simple/browser' - -const overlay = await Overlay.create({ topics: ['tm_payments'], network: 'mainnet' }) - -// Advertise hosting -await wallet.advertiseSHIP('https://myserver.com', 'tm_payments', 'ship-tokens') -await wallet.advertiseSLAP('https://myserver.com', 'ls_payments', 'slap-tokens') - -// Broadcast + query -const { txid, broadcast } = await wallet.broadcastAction(overlay, { - outputs: [{ lockingScript: '...', satoshis: 1, outputDescription: 'Overlay output' }], - description: 'Overlay broadcast' -}, ['tm_payments']) - -const results = await overlay.lookupOutputs('ls_payments', { tag: 'recent' }) -``` - ---- - -## 9. Type Reference - -### Core Types - -```typescript -type Network = 'main' | 'testnet' - -interface WalletDefaults { - network: Network; description: string; outputDescription: string - tokenBasket: string; tokenProtocolID: [SecurityLevel, string]; tokenKeyID: string - messageBoxHost: string; registryUrl?: string - didBasket: string; didResolverUrl: string; didProxyUrl?: string; didProtocolID: [SecurityLevel, string] -} - -interface PaymentOptions { - to: string; satoshis: number; memo?: string; description?: string -} - -interface SendOptions { outputs: SendOutputSpec[]; description?: string } - -interface SendOutputSpec { - to?: string; satoshis?: number; data?: Array - description?: string; basket?: string; protocolID?: [number, string]; keyID?: string -} -// Rules: to only → P2PKH | data only → OP_RETURN | to + data → PushDrop - -interface TransactionResult { txid: string; tx: any; outputs?: OutputInfo[] } -interface SendResult extends TransactionResult { outputDetails: SendOutputDetail[] } -``` - -### Token Types - -```typescript -interface TokenOptions { to?: string; data: any; basket?: string; protocolID?: [number, string]; keyID?: string; satoshis?: number } -interface TokenResult extends TransactionResult { basket: string; encrypted: boolean } -interface TokenDetail { outpoint: string; satoshis: number; data: any; protocolID: any; keyID: string; counterparty: string } -interface SendTokenOptions { basket: string; outpoint: string; to: string } -interface RedeemTokenOptions { basket: string; outpoint: string } -``` - -### Inscription Types - -```typescript -type InscriptionType = 'text' | 'json' | 'file-hash' | 'image-hash' -interface InscriptionResult extends TransactionResult { type: InscriptionType; dataSize: number; basket: string } -``` - -### Server Wallet Types - -```typescript -interface ServerWalletConfig { privateKey: string; network?: Network; storageUrl?: string } -interface PaymentRequest { serverIdentityKey: string; derivationPrefix: string; derivationSuffix: string; satoshis: number; memo?: string } -interface IncomingPayment { tx: number[] | Uint8Array; senderIdentityKey: string; derivationPrefix: string; derivationSuffix: string; outputIndex: number; description?: string } -interface DirectPaymentResult extends TransactionResult { senderIdentityKey: string; derivationPrefix: string; derivationSuffix: string; outputIndex: number } -``` - -### DID Types - -```typescript -interface DIDDocument { '@context': string[]; id: string; controller: string; verificationMethod: DIDVerificationMethod[]; authentication: string[]; assertionMethod: string[] } -interface DIDParseResult { method: string; identifier: string } -``` - -### Credential Types - -```typescript -interface CredentialSchemaConfig { id: string; name: string; description?: string; certificateTypeBase64?: string; fields: CredentialFieldSchema[]; validate?: (values) => string | null; computedFields?: (values) => Record } -interface CredentialIssuerConfig { privateKey: string; schemas?: CredentialSchemaConfig[]; revocation?: { enabled: boolean; wallet?: any; store?: RevocationStore } } -interface VerifiableCredential { '@context': string[]; type: string[]; issuer: string; issuanceDate: string; credentialSubject: { id: string; [key: string]: any }; proof: { type: string; signatureValue: string; ... }; _bsv: { certificate: CertificateData } } -interface VerifiablePresentation { '@context': string[]; type: string[]; holder: string; verifiableCredential: VerifiableCredential[]; proof: { ... } } -interface RevocationStore { save: (sn: string, record: RevocationRecord) => Promise; load: (sn: string) => Promise; delete: (sn: string) => Promise; has: (sn: string) => Promise; findByOutpoint: (op: string) => Promise } -``` - -### Overlay Types - -```typescript -interface OverlayConfig { topics: string[]; network?: 'mainnet' | 'testnet' | 'local'; slapTrackers?: string[]; hostOverrides?: Record; additionalHosts?: Record } -interface OverlayBroadcastResult { success: boolean; txid?: string; code?: string; description?: string } -interface OverlayOutput { beef: number[]; outputIndex: number; context?: number[] } -``` - -### Server Handler Config Types - -```typescript -interface IdentityRegistryConfig { store?: IdentityRegistryStore; validateTag?: (tag: string, identityKey: string) => string | null; maxTagsPerIdentity?: number } -interface IdentityRegistryStore { load: () => RegistryEntry[]; save: (entries: RegistryEntry[]) => void } -interface RegistryEntry { tag: string; identityKey: string; createdAt: string } -interface DIDResolverConfig { resolverUrl?: string; wocBaseUrl?: string; resolverTimeout?: number; maxHops?: number } -interface ServerWalletManagerConfig { envVar?: string; keyFile?: string; network?: Network; storageUrl?: string; defaultRequestSatoshis?: number; requestMemo?: string } -interface CredentialIssuerHandlerConfig { schemas: CredentialSchemaConfig[]; envVar?: string; keyFile?: string; serverWalletManager?: any; revocationStorePath?: string } -``` - -### Error Classes - -```typescript -SimpleError // base (code?: string) -├── WalletError // WALLET_ERROR -├── TransactionError // TRANSACTION_ERROR -├── MessageBoxError // MESSAGEBOX_ERROR -├── CertificationError // CERTIFICATION_ERROR -├── DIDError // DID_ERROR -└── CredentialError // CREDENTIAL_ERROR -``` - -### Default Configuration - -```typescript -{ - network: 'main', - description: 'BSV-Simplify transaction', - outputDescription: 'BSV-Simplify output', - tokenBasket: 'tokens', - tokenProtocolID: [0, 'token'], - tokenKeyID: '1', - messageBoxHost: 'https://messagebox.babbage.systems', - registryUrl: undefined, - didBasket: 'did-chain', - didResolverUrl: 'https://bsvdid-universal-resolver.nchain.systems', - didProtocolID: [0, 'bsvdid'] -} -``` +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/helpers/ts-templates/.github/workflows/push.yaml b/packages/helpers/ts-templates/.github/workflows/push.yaml deleted file mode 100644 index c93f87e49..000000000 --- a/packages/helpers/ts-templates/.github/workflows/push.yaml +++ /dev/null @@ -1,73 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/helpers/ts-templates/AGENTS.md b/packages/helpers/ts-templates/AGENTS.md index 791091c1b..dcea67c80 100644 --- a/packages/helpers/ts-templates/AGENTS.md +++ b/packages/helpers/ts-templates/AGENTS.md @@ -1,143 +1,10 @@ -# CLAUDE.md — @bsv/templates +# ts-stack agent instructions -## Purpose (1-2 sentences) +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -Low-level BSV script templates library. Provides reusable locking/unlocking script implementations (OpReturn, MultiPushDrop, P2MSKH) for common and advanced Bitcoin SV patterns without abstracting away control. - -## Public API surface - -### OpReturn -- `new OpReturn()` — Factory; stateless -- `.lock(data: string | string[] | number[], enc?: 'hex' | 'utf8' | 'base64'): LockingScript` — Create non-spendable OP_RETURN script -- `.unlock()` — Throws: OpReturn is read-only -- `OpReturn.decode(script: Script): string[]` — Static method; decode OP_RETURN data to UTF-8 strings - -### MultiPushDrop -- `new MultiPushDrop()` — Factory -- `.lock(fields: number[][], protocolID: WalletProtocol, keyID: string, counterparties: string[], reasonabilityLimit?: boolean): Promise` — Create encrypted data token with multiple trusted owners -- `.unlock(protocolID: WalletProtocol, keyID: string, counterparties: string[], redeemPath?: string): UnlockingTemplate` — Spend MultiPushDrop (sign + estimateLength) - -### P2MSKH (Pay-to-Multisig-Key-Hash) -- `new P2MSKH(m: number, n: number, wallet?: WalletInterface)` — Create m-of-n multisig template -- `.lock(params: LockParams): Promise` — Create multisig locking script -- `.unlock(params: UnlockParams): UnlockingTemplate` — Spend multisig (requires m signatures) - -### Types (from @bsv/sdk) -- `LockingScript` — Serializable script object -- `UnlockingScript` — Script that unlocks a locked output -- `UnlockingTemplate` — `{ sign(tx, inputIndex), estimateLength() }` -- `ScriptTemplate` — Interface with `lock()` and `unlock()` methods -- `Transaction`, `Script`, `OP` — Core SDK types - -## Real usage patterns - -```typescript -import { Transaction, PrivateKey } from '@bsv/sdk' -import { OpReturn, MultiPushDrop, P2MSKH } from '@bsv/templates' - -// 1. Create OP_RETURN transaction -const tx = new Transaction() -const opReturn = new OpReturn() -tx.addOutput({ - lockingScript: opReturn.lock(['APP', JSON.stringify({ action: 'vote' })]), - satoshis: 0 -}) -await tx.sign() -console.log(tx.id()) - -// 2. Decode OP_RETURN data -const decodedData = OpReturn.decode(tx.outputs[0].lockingScript) -console.log(decodedData) // ['APP', '{"action":"vote"}'] - -// 3. Create MultiPushDrop token with 2 trusted owners -const pushDrop = new MultiPushDrop() -const lockingScript = await pushDrop.lock( - [[1, 2, 3], [4, 5, 6]], // Two fields - [2, 'token'], // protocol - 'key-1', - ['owner1-pubkey', 'owner2-pubkey'], // Both can unlock - true // reasonableness limit -) - -const tx2 = new Transaction() -tx2.addOutput({ lockingScript, satoshis: 1 }) - -// 4. Spend MultiPushDrop -const unlocker = pushDrop.unlock( - [2, 'token'], - 'key-1', - ['owner1-pubkey', 'owner2-pubkey'] -) -const unlockingScript = await unlocker.sign(tx2, 0) // Sign input 0 - -// 5. Create 2-of-3 multisig -const p2mskh = new P2MSKH(2, 3) -const multiSigLock = await p2mskh.lock({ - publicKeys: [pubkey1, pubkey2, pubkey3] -}) - -const tx3 = new Transaction() -tx3.addOutput({ lockingScript: multiSigLock, satoshis: 10000 }) - -// Spend 2-of-3 (need signatures from 2 keys) -const multiSigUnlocker = p2mskh.unlock({ - publicKeys: [pubkey1, pubkey2, pubkey3], - signingKeys: [privkey1, privkey2] // Supply 2 of 3 private keys -}) -const unlockingScript2 = await multiSigUnlocker.sign(tx3, 0) -``` - -## Key concepts - -- **ScriptTemplate Interface** — Implements `lock()` to create locking script and `unlock()` to sign/spend -- **OP_RETURN** — Immutable, non-spendable data storage; standard for metadata -- **PushDrop** — Encrypted data format with multi-trusted-owner support; fields are encrypted -- **Multisig** — M-of-N threshold signing; requires m private keys to unlock -- **Wallet Integration** — Templates accept WalletInterface for wallet-compatible key derivation (BRC-29, BRC-42) -- **Direct Key Mode** — Can also use raw public/private keys without wallet -- **Protocol ID** — Identifier for script family; used in wallet derivation contexts -- **Reasonableness Limit** — Anti-DoS measure for PushDrop templates - -## Dependencies - -**Runtime:** -- `@bsv/sdk` ^2.0.14 (Transaction, Script, OP, LockingScript, etc.) - -**Dev:** -- TypeScript, Jest, ts-jest, Oxlint - -## Common pitfalls / gotchas - -1. **OP_RETURN is read-only** — Cannot spend OP_RETURN outputs; used for data only -2. **Lock/Unlock consistency** — Lock and unlock must use same protocol ID, key ID, and counterparty parameters -3. **Wallet context required** — Some templates (MultiPushDrop, P2MSKH) require WalletInterface if using wallet derivation; pass explicitly or as constructor arg -4. **Signature generation async** — All `unlock().sign()` calls are async; use await -5. **OP_RETURN encoding** — Data is UTF-8 by default; if you need binary, encode as hex first and specify `enc: 'hex'` -6. **Script serialization** — LockingScript must be converted to hex before adding to transaction (`script.toHex()`) -7. **Multisig key order** — Public keys must be in exact order when constructing; different order = different script hash - -## Spec conformance - -- **OP_RETURN** — Standard Bitcoin data format -- **BRC-95** — PushDrop token format -- **BRC-29** — Hierarchical key derivation (in wallet context) -- **BRC-42** — Public key derivation (in wallet context) -- **Bitcoin Script** — All scripts are valid Bitcoin SV scripts - -## File map - -``` -ts-templates/ - src/ - OpReturn.ts # OP_RETURN template - MultiPushDrop.ts # PushDrop multi-owner template - P2MSKH.ts # Pay-to-Multisig-Key-Hash template - mod.ts # Main entrypoint (re-exports) - tests/ (if present) # Unit tests for each template -``` - -## Integration points - -- **Depends on:** `@bsv/sdk` (core types and utilities) -- **Used by:** `@bsv/wallet-helper` (higher-level abstraction over these templates), `@bsv/simple` (wallet-level operations) -- **Complements:** Applications building custom script workflows; developers needing low-level script control +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/helpers/ts-templates/CHANGELOG.md b/packages/helpers/ts-templates/CHANGELOG.md index 36afb45b2..53f603990 100644 --- a/packages/helpers/ts-templates/CHANGELOG.md +++ b/packages/helpers/ts-templates/CHANGELOG.md @@ -14,6 +14,8 @@ All notable changes to this project will be documented in this file. The format - (Include new features or significant user-visible enhancements here.) ### Changed +- Point contributors and AI agents to the canonical stack-level contribution + and quality policy without changing template behavior. - Consolidate MultiPushDrop script-chunk assembly without changing the generated locking script. diff --git a/packages/helpers/ts-templates/CONTRIBUTING.md b/packages/helpers/ts-templates/CONTRIBUTING.md deleted file mode 100644 index 95a3a7c5e..000000000 --- a/packages/helpers/ts-templates/CONTRIBUTING.md +++ /dev/null @@ -1,85 +0,0 @@ -# CONTRIBUTING to `@bsv/templates` - -Thank you for considering contributing to the BSV Blockchain Script Templates Project! This document outlines the processes and practices we expect contributors to adhere to. - -## Table of Contents - -1. [General Guidelines](#general-guidelines) -2. [Code of Conduct](#code-of-conduct) -3. [Getting Started](#getting-started) -4. [Pull Request Process](#pull-request-process) -5. [Coding Conventions](#coding-conventions) -6. [Documentation and Testing](#documentation-and-testing) -7. [Contact & Support](#contact--support) - -## General Guidelines - -- **Issues First**: If you're planning to add a new template or change existing behavior, please open an issue first. This allows us to avoid multiple people working on similar templates and provides a place for discussion. - -- **Stay Updated**: Always pull the latest changes from the main branch before creating a new branch or starting on new code. - -- **Simplicity Over Complexity**: Your template should be as simple as possible, given the requirements. - -## Code of Conduct - -### Posting Issues and Comments - -- **Be Respectful**: Everyone is here to help and grow. Avoid any language that might be considered rude or offensive. - -- **Be Clear and Concise**: Always be clear about what you're suggesting or reporting. If an issue is related to a particular piece of code or a specific error message, include that in your comment. - -- **Stay On Topic**: Keep the conversation relevant to the issue at hand. If you have a new idea or unrelated question, please open a new issue. - -### Coding and PRs - -- **Stay Professional**: Avoid including "fun" code, comments, or irrelevant file changes in your commits and pull requests. - -## Getting Started - -1. **Fork the Repository**: Click on the "Fork" button at the top-right corner of this repository. - -2. **Clone the Forked Repository**: `git clone https://github.com/YOUR_USERNAME/ts-templates.git` - -3. **Navigate to the Directory**: `cd ts-templates` - -4. **Install Dependencies**: Always run `npm i` after pulling to ensure tooling is up to date. - -## Pull Request Process - -1. **Create a Branch**: For every new feature or bugfix, create a new branch. - -2. **Commit Your Changes**: Make your changes and commit them. Commit messages should be clear and concise to explain what was done. - -3. **Run Tests**: Ensure all tests pass using Jest: `npm test`. - -4. **Documentation**: All code must be fully annotated with comments. - -5. **Push to Your Fork**: `git push origin your-new-branch`. - -6. **Open a Pull Request**: Go to your fork on GitHub and click "New Pull Request". Fill out the PR template, explaining your changes. - -7. **Code Review**: At least one maintainer must review and approve the PR before it's merged. Address any feedback or changes requested. - -8. **Merge**: Once approved, the PR will be merged into the main branch. - -## Coding Conventions - -- **Code Style**: We use `Oxlint` for our TypeScript coding style. Run `npm run lint` to ensure your code adheres to this style. - -- **Minimal Runtime Dependencies**: Code should not depend on external systems where possible, with a strong preference for maintaining things internally. The one exception is the `@bsv/sdk` library dependency. - -- **Testing**: Always include tests for new code or changes. We aim for industry-standard levels of test coverage. - -- **Documentation**: All functions, classes, and modules should be documented. Use annotation comments to describe the purpose, parameters, and return values. - -## Documentation and Testing - -- **Documentation**: Update the documentation whenever you add or modify the code. - -- **Testing**: We use Jest for all tests. Write comprehensive tests, ensuring edge cases are covered. All PRs should maintain or improve the current test coverage. - -## Contact & Support - -If you have any questions or need assistance with your contributions, feel free to reach out. Remember, we're here to help each other grow and improve the `@bsv/templates`. - -Thank you for being a part of this journey. Your contributions help shape the future of the BSV Blockchain! diff --git a/packages/helpers/ts-templates/README.md b/packages/helpers/ts-templates/README.md index a50835e46..9ed40130b 100644 --- a/packages/helpers/ts-templates/README.md +++ b/packages/helpers/ts-templates/README.md @@ -27,11 +27,11 @@ tx.addOutput({ ## Current Templates -Name | Description ---------------------------------|-------------------------- -[OpReturn](./src/OpReturn.ts) | Tag data in a non-spendable script -[Metant](./src/Metanet.ts) | Create transactions that follow the Metanet protocol -[MultiPushDrop](./src/MultiPushDrop.ts) | Create data tokens with multiple trusted owners +| Name | Description | +| --------------------------------------- | ---------------------------------------------------- | +| [OpReturn](./src/OpReturn.ts) | Tag data in a non-spendable script | +| [Metant](./src/Metanet.ts) | Create transactions that follow the Metanet protocol | +| [MultiPushDrop](./src/MultiPushDrop.ts) | Create data tokens with multiple trusted owners | ## Contribution Guidelines @@ -43,7 +43,8 @@ We're always looking for contributors to add the coolest new templates. Whatever 4. **Test**: Ensure all tests pass by running `npm test`. 5. **Commit**: Commit your changes and push to your fork. 6. **Pull Request**: Open a pull request from your fork to this repository. -For more details, check the [contribution guidelines](./CONTRIBUTING.md). + For more details, check the + [repository contribution guidelines](https://github.com/bsv-blockchain/ts-stack/blob/main/CONTRIBUTING.md). For information on past releases, check out the [changelog](./CHANGELOG.md). For future plans, check the [roadmap](./ROADMAP.md)! diff --git a/packages/helpers/ts-templates/package.json b/packages/helpers/ts-templates/package.json index 6361721b9..54958c205 100644 --- a/packages/helpers/ts-templates/package.json +++ b/packages/helpers/ts-templates/package.json @@ -1,6 +1,6 @@ { "name": "@bsv/templates", - "version": "1.9.5", + "version": "1.9.6", "sideEffects": false, "engines": { "node": ">=22" diff --git a/packages/messaging/authsocket-client/.github/workflows/push.yaml b/packages/messaging/authsocket-client/.github/workflows/push.yaml deleted file mode 100644 index c93f87e49..000000000 --- a/packages/messaging/authsocket-client/.github/workflows/push.yaml +++ /dev/null @@ -1,73 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/messaging/authsocket-client/AGENTS.md b/packages/messaging/authsocket-client/AGENTS.md index 85d499ab6..dcea67c80 100644 --- a/packages/messaging/authsocket-client/AGENTS.md +++ b/packages/messaging/authsocket-client/AGENTS.md @@ -1,94 +1,10 @@ -# CLAUDE.md — @bsv/authsocket-client +# ts-stack agent instructions -## Purpose -Client-side BRC-103 mutual authentication wrapper for socket.io-client. Signs all outbound messages and verifies inbound messages using a wallet, enabling authenticated peer-to-peer WebSocket communication. +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -## Public API surface - -- **AuthSocketClient** (function): `AuthSocketClient(serverUrl, options)` - - Wraps socket.io-client with BRC-103 authentication - - Options: `wallet` (BRC-100 wallet), `managerOptions` (Socket.IO manager options) - - Returns proxy-like socket object with `.on()`, `.emit()`, `.id`, `.connect()`, `.disconnect()` - - All messages automatically signed with client wallet key - -- **SocketClientTransport** (internal): implements BRC-103 `Transport` interface - - Receives raw BRC-103 frames via `socket.on('authMessage', ...)` - - Routes to internal `Peer` for handshake/signature verification - - Sends BRC-103 responses via `socket.emit('authMessage', ...)` - -## Real usage patterns - -From README: -```ts -import { AuthSocketClient } from '@bsv/authsocket-client' -import { ProtoWallet } from '@bsv/sdk' - -// Load client wallet -const clientWallet = new ProtoWallet('client-private-key-hex') - -// Create authenticated socket -const socket = AuthSocketClient('http://localhost:3000', { wallet: clientWallet }) - -// Standard Socket.IO usage — messages auto-signed -socket.on('connect', () => { - console.log('Connected. Socket ID:', socket.id) - socket.emit('chatMessage', { text: 'Hello from client!' }) -}) - -socket.on('chatMessage', (msg) => { - console.log('Server says:', msg) -}) - -socket.on('disconnect', () => { - console.log('Disconnected') -}) -``` - -## Key concepts - -- **BRC-103 mutual authentication**: Nonce-based challenge-response protocol -- **Ephemeral nonces**: Each outbound message includes fresh nonce + signature -- **Signature verification**: Inbound messages verified against server's public key -- **Session binding**: Server nonce proves server identity; client nonce proves client identity -- **Certificate exchange**: Supports verifiable certificates during handshake (optional) -- **Transparent proxying**: User code interacts with normal Socket.IO API; BRC-103 is hidden - -## Dependencies - -- `@bsv/sdk` ^2.0.14 — BRC-103 `Peer`, `Transport`, crypto, wallet -- `socket.io-client` ^4.8.1 — WebSocket client -- Dev: jest, ts-jest, TypeScript - -## Common pitfalls / gotchas - -1. **Wallet must be BRC-100 compatible** — needs `sign()` and `verify()` methods for identity -2. **Server must also support BRC-103** — use `@bsv/authsocket` or implement BRC-103 `Peer` yourself -3. **Nonce tracking** — library auto-generates nonces; don't manually set them -4. **Signature verification** — inbound messages automatically verified; if verification fails, message is dropped -5. **Connection state** — `.connect()` and `.disconnect()` trigger standard Socket.IO lifecycle -6. **Message order** — BRC-103 handshake must complete before general messages; library handles this - -## Spec conformance - -- **BRC-103** (Peer-to-Peer Mutual Authentication): Full handshake, nonce exchange, signature verification, optional certificate exchange -- Uses BRC-103 `Peer` and `Transport` abstractions from SDK -- Client transport implements ephemeral nonce generation + signing - -## File map - -``` -/Users/personal/git/ts-stack/packages/messaging/authsocket-client/ - src/ - index.ts — main exports (AuthSocketClient) - AuthSocketClient.ts — function wrapping socket.io-client - SocketClientTransport.ts — BRC-103 Transport implementation - tests/ - *.test.ts — unit & integration tests -``` - -## Integration points - -- **authsocket** — Paired server-side package; together they form end-to-end BRC-103 WebSocket auth -- **@bsv/sdk** — Supplies `Peer`, `Transport`, wallet interfaces, crypto utilities -- **socket.io-client** — Underlying WebSocket client library -- **message-box-client** — Uses `AuthSocketClient` for live message delivery +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/messaging/authsocket/.github/workflows/push.yaml b/packages/messaging/authsocket/.github/workflows/push.yaml deleted file mode 100644 index c93f87e49..000000000 --- a/packages/messaging/authsocket/.github/workflows/push.yaml +++ /dev/null @@ -1,73 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/messaging/authsocket/AGENTS.md b/packages/messaging/authsocket/AGENTS.md index b264e2294..dcea67c80 100644 --- a/packages/messaging/authsocket/AGENTS.md +++ b/packages/messaging/authsocket/AGENTS.md @@ -1,112 +1,10 @@ -# CLAUDE.md — @bsv/authsocket +# ts-stack agent instructions -## Purpose -Server-side BRC-103 mutual authentication wrapper for Socket.IO. Enforces cryptographic signing and verification on all WebSocket messages, enabling peer-to-peer identity verification and certificate exchange. +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -## Public API surface - -- **AuthSocketServer** (constructor): `new AuthSocketServer(httpServer, options)` - - Wraps an HTTP server to add BRC-103 authentication to Socket.IO - - Options: `wallet` (BRC-100 wallet), `cors` (Socket.IO CORS config), `sessionManager`, `certificatesToRequest` - - Returns Socket.IO–like `io` object that emits authenticated `AuthSocket` instances on `'connection'` - - Lifecycle: idempotent `close()` disconnects clients and closes the attached HTTP server - -- **AuthSocket** (event handlers): wraps native Socket.IO socket - - `.on(eventName, callback)` — listen for authenticated messages - - `.emit(eventName, data)` — send authenticated message - - `.id` — socket ID (string) - - Standard Socket.IO interface; all messages auto-signed/verified - -- **SocketServerTransport** (internal): implements BRC-103 `Transport` interface - - Receives raw BRC-103 frames via `socket.on('authMessage', ...)` - - Routes to internal `Peer` for handshake/signature verification - - Sends BRC-103 responses via `socket.emit('authMessage', ...)` - -## Real usage patterns - -From tests: -```ts -// Initialize server with wallet -const serverWallet = new ProtoWallet('my-private-key-hex') -const io = new AuthSocketServer(server, { wallet: serverWallet, cors: { origin: '*' } }) - -// Listen for authenticated connections -io.on('connection', (socket) => { - console.log('Authenticated socket:', socket.id) - - // Receive message — signature verified automatically - socket.on('chatMessage', (msg) => { - console.log('Verified message:', msg) - }) - - // Send message — signed automatically - socket.emit('chatMessage', { from: socket.id, text: 'Hello client' }) -}) - -server.listen(3000) -``` - -Transport test: -```ts -const socket = createMockSocket() -const transport = new SocketServerTransport(socket) -const message = { type: 'test', payload: [1, 2, 3] } - -await transport.send(message) -expect(socket.emit).toHaveBeenCalledWith('authMessage', message) - -// Receiving messages -await transport.onData(callback) -socket._fire('authMessage', message) -expect(callback).toHaveBeenCalledWith(message) -``` - -## Key concepts - -- **BRC-103 mutual authentication**: Nonce-based challenge-response with signatures -- **Ephemeral sockets**: Each new Socket.IO connection creates a new BRC-103 `Peer` -- **Session management**: Tracks nonces and authentication state per socket via `SessionManager` -- **Certificate exchange**: Supports requesting and verifying verifiable certificates during handshake -- **Message signing**: Every outbound message signed with server wallet; every inbound message verified -- **Automatic re-dispatch**: Special `'authMessage'` channel used for BRC-103 frames; user code sees normal Socket.IO events - -## Dependencies - -- `@bsv/sdk` ^2.0.14 — BRC-103 `Peer`, `Transport`, crypto utilities -- `socket.io` ^4.8.1 — WebSocket server -- Dev: jest, ts-jest, TypeScript - -## Common pitfalls / gotchas - -1. **Wallet must be BRC-100 compatible** — needs `sign()` and `verify()` methods -2. **Certificate requests optional** — if you don't request them, handshake is faster -3. **No auto-reconnect** — client-side library handles reconnection logic -4. **Each socket = separate peer** — nonce state is per-socket; don't mix them -5. **CORS must be set explicitly** — Socket.IO requires `cors` config for browser clients -6. **Async message handling** — all socket operations should `await` promises - -## Spec conformance - -- **BRC-103** (Peer-to-Peer Mutual Authentication): Full handshake, nonce exchange, signature verification -- Uses BRC-103 `Peer` and `Transport` abstractions from SDK -- Session manager implements nonce replay protection - -## File map - -``` -/Users/personal/git/ts-stack/packages/messaging/authsocket/ - src/ - index.ts — main exports (AuthSocketServer) - AuthSocketServer.ts — wraps Socket.IO with BRC-103 - AuthSocket.ts — Socket.IO socket wrapper (signing/verification) - SocketServerTransport.ts — BRC-103 Transport implementation - tests/ - *.test.ts — unit & integration tests -``` - -## Integration points - -- **authsocket-client** — Paired with this package; client handles the socket.io-client + BRC-103 side -- **@bsv/sdk** — Supplies `Peer`, `Transport`, `SessionManager`, wallet abstractions -- **socket.io** — Underlying WebSocket transport layer -- Can be combined with `auth-express-middleware` on same server for HTTP + WS mutual auth +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/messaging/message-box-client/AGENTS.md b/packages/messaging/message-box-client/AGENTS.md index e9f59bfe0..dcea67c80 100644 --- a/packages/messaging/message-box-client/AGENTS.md +++ b/packages/messaging/message-box-client/AGENTS.md @@ -1,101 +1,10 @@ -# Message Box Client Maintainer Guide +# ts-stack agent instructions -## Scope +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -`@bsv/message-box-client` is the browser- and Node-compatible client for the -Message Box HTTP/WebSocket protocol. It provides encrypted store-and-forward -messaging, BRC-29 peer payments, token-settlement adapters, delivery -permissions and quotes, and push-device registration. - -Public entry points are exported from `mod.ts`. Preserve both ESM and CommonJS -consumers and the UMD browser bundle. - -## Security and compatibility invariants - -- Messages are encrypted per recipient by default. Plaintext requires an - explicit `skipEncryption: true`. -- A shared multi-recipient body cannot be encrypted per recipient. The batch - API therefore requires explicit plaintext opt-in; notification convenience - APIs send bounded individual encrypted requests. -- Explicitly configured hosts may use HTTP for local/operator-controlled - environments. Overlay-advertised hosts are untrusted and must be public - HTTPS destinations; reject loopback, private, link-local, reserved, and - documentation-only targets. -- Preserve configured URL path prefixes when constructing route URLs. -- BRC-103 authentication, recipient ownership, permission/payment checks, and - encryption are the access boundaries. Message Box is a public protocol - service; compatible servers use credential-free wildcard CORS by default and - may opt into an exact allowlist. -- Keep `sendMesagetoRecepients` as a deprecated compatibility wrapper around - the correctly spelled `sendMessageToRecipients`. -- Never silently contact a deployed integration target. Live integration tests - require explicit environment configuration and a separate acknowledgement - for `*.bsvb.tech`. - -## Server contract - -Authenticated routes: - -- `POST /sendMessage` -- `POST /listMessages` (bounded pagination) -- `POST /acknowledgeMessage` -- `POST /registerDevice` -- `GET /devices` -- `POST /permissions/set` -- `GET /permissions/get` -- `GET /permissions/list` -- `GET /permissions/quote` - -Live delivery uses authenticated Socket.IO rooms named -`{identityKey}-{messageBox}`. The reviewed source contract is -`specs/messaging/message-box-http.yaml`. - -## File map - -- `mod.ts` — public exports -- `src/MessageBoxClient.ts` — base HTTP, WebSocket, overlay, permission, quote, - device, and message APIs -- `src/PeerPayClient.ts` — BRC-29 payments and payment requests -- `src/PeerTokenClient.ts` — token transfer/request transport -- `src/TokenSettlementAdapter.ts` — token-standard adapter contract -- `src/RemittanceAdapter.ts` — SDK remittance integration -- `src/host.ts` — configured-host and untrusted-overlay URL policy -- `src/types.ts`, `src/types/permissions.ts` — public types -- `src/__tests/` — deterministic unit and contract tests -- `src/__tests/integration/` — explicitly configured live tests -- `tsdown.config.ts` — unbundled ESM/CommonJS/declaration build -- `webpack.config.js` — UMD build -- `browser-budget.json` — Vite, esbuild, and UMD size/composition budgets - -## Required checks - -Run from the repository root: - -```bash -pnpm --filter @bsv/message-box-client typecheck -pnpm --filter @bsv/message-box-client format:check -pnpm --filter @bsv/message-box-client lint -pnpm --filter @bsv/message-box-client test -pnpm --filter @bsv/message-box-client test:coverage -pnpm --filter @bsv/message-box-client pack:check -pnpm --filter @bsv/message-box-client test:browser -``` - -The integration suite additionally requires: - -```bash -MESSAGE_BOX_RUN_INTEGRATION=true \ -MESSAGE_BOX_INTEGRATION_HOST=http://127.0.0.1:8080 \ -MESSAGE_BOX_WALLET_ORIGINATOR=localhost \ -pnpm --filter @bsv/message-box-client test:integration -``` - -Do not weaken browser budgets to hide growth. Update a budget only with measured -artifact evidence and an explanation. - -## Release contract - -The tarball contains compiled `dist/` artifacts, declarations, source maps, -README, and license only. It must not contain source tests, coverage, editor -files, package-manager locks, or repository workflows. Do not bump or publish a -version as an incidental part of maintenance work. +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/messaging/ts-paymail/.github/ISSUE_TEMPLATE/bug_report.md b/packages/messaging/ts-paymail/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 4fc56f13f..000000000 --- a/packages/messaging/ts-paymail/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: Bug Report -about: Report a bug or an issue you've found with `@bsv/sdk`. -title: "[BUG]" -labels: bug -assignees: '' - ---- - -## Bug Description - -Briefly describe the bug/issue you've encountered. - -## Steps to Reproduce - -1. Step 1 -2. Step 2 -3. ... - -## Expected Behavior - -What should have happened if the bug hadn't occurred? - -## Actual Behavior - -What actually happened? - -## Stack Traces or Screenshots - -If applicable, add screenshots or stack traces to help explain the issue. - -## Environment - -- OS: [e.g. MacOS, Windows] -- Node version: [e.g. 12.20] -- `@bsv/sdk` version: [e.g. 1.2.3] - -## Additional Information - -Provide any additional context or information about the bug. diff --git a/packages/messaging/ts-paymail/.github/ISSUE_TEMPLATE/discussion.md b/packages/messaging/ts-paymail/.github/ISSUE_TEMPLATE/discussion.md deleted file mode 100644 index 52aa083ce..000000000 --- a/packages/messaging/ts-paymail/.github/ISSUE_TEMPLATE/discussion.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Discussion -about: Propose a discussion or seek clarification about a feature or topic. -title: "[DISCUSSION]" -labels: discussion -assignees: '' - ---- - -## Summary - -Briefly describe the topic you'd like to discuss. - -## Motivation - -Why do you believe this to be important? - -## Description - -Provide a detailed description or elaborate on your topic. - -## Additional References - -Provide any additional articles, links, or context that would help facilitate the discussion. diff --git a/packages/messaging/ts-paymail/.github/pull_request_template.md b/packages/messaging/ts-paymail/.github/pull_request_template.md deleted file mode 100644 index f42fea9fc..000000000 --- a/packages/messaging/ts-paymail/.github/pull_request_template.md +++ /dev/null @@ -1,23 +0,0 @@ -## Description of Changes - -Provide a brief description of the changes you've made. - -## Linked Issues / Tickets - -Reference any related issues or tickets, e.g. "Closes #123". - -## Testing Procedure - -Describe the tests you've added or any testing steps you've taken. - -- [ ] I have added new unit tests -- [ ] All tests pass locally -- [ ] I have tested manually in my local environment - -## Checklist: - -- [ ] I have performed a self-review of my own code -- [ ] I have made corresponding changes to the documentation -- [ ] My changes generate no new warnings -- [ ] I have updated `CHANGELOG.md` with my changes -- [ ] I have run `npm run doc` and `npm run lint` one final time before requesting a review diff --git a/packages/messaging/ts-paymail/.github/workflows/push.yaml b/packages/messaging/ts-paymail/.github/workflows/push.yaml deleted file mode 100644 index c93f87e49..000000000 --- a/packages/messaging/ts-paymail/.github/workflows/push.yaml +++ /dev/null @@ -1,73 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/messaging/ts-paymail/AGENTS.md b/packages/messaging/ts-paymail/AGENTS.md index ba3636298..dcea67c80 100644 --- a/packages/messaging/ts-paymail/AGENTS.md +++ b/packages/messaging/ts-paymail/AGENTS.md @@ -1,99 +1,10 @@ -# AGENTS.md — `@bsv/paymail` +# ts-stack agent instructions -## Purpose +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -`@bsv/paymail` is the TypeScript stack's Paymail client and Express routing -library. It implements capability discovery and typed flows for profiles, PKI, -P2P transaction delivery, transaction negotiation, and simple ordinals. - -## Supported surfaces - -- Root: `@bsv/paymail` -- Client: `@bsv/paymail/client` -- Capabilities: `@bsv/paymail/capability` -- Server router: `@bsv/paymail/router` -- Errors: `@bsv/paymail/errors` - -The root browser condition intentionally exports the client, capabilities, and -errors without importing Express or server routes. Preserve every declared -subpath and its ESM/CommonJS declaration pairing when changing the build. - -## Client contract - -Construct `PaymailClient` with optional `HttpClient`, `DNSResolverOptions`, and -localhost port arguments. `getCapabilities` is the public alias for -`getDomainCapabilities`; capabilities are cached per client instance. - -The default HTTP transport serializes JSON requests and enforces a 30-second -timeout. Unit tests must use injected HTTP and DNS fakes. Do not add tests that -depend on public DNS, Paymail providers, ARC, or other live services. - -P2P signatures use compact Base64 Bitcoin Signed Message encoding. The raw, -BEEF, and ordinal receive routes must accept the same encoding produced by -`PaymailClient.createP2PSignature`. Keep malformed length/header rejection and -transaction-ID signature checks covered. Verify the signature locally before -performing an outbound Paymail ownership lookup. - -## Router contract - -`domainLogicHandler` receives: - -1. a route-parameter object containing `paymail` and any other path parameters; -2. the validated and unknown-field-stripped request body, when present; and -3. an optional public key for route implementations that supply one. - -Use `PaymailRoute.getNameAndDomain(params)` to parse the handle. A router's -`baseUrl` is the externally reachable origin advertised by -`/.well-known/bsvalias`; `basePath` is optional. - -The package deliberately does not impose CORS or CSP. These endpoints are often -public services called by applications, WUI clients, mobile devices, webviews, -and unknown future origins. Host applications and edge infrastructure must keep -credential-free public access broadly available by default where required, -while allowing explicit origin allowlists or credentialed modes as operator -configuration. Never treat CORS as authorization or combine wildcard origins -with credentials. - -## Build and publication - -`tsdown.config.ts` produces unbundled ESM and CommonJS files, module-specific -declarations, source maps, and a client-only browser root. Do not restore the -legacy project-reference build or publish source/test/example files. - -The package is published only by the repository's coordinated release workflow. -Do not run `npm publish` or `pnpm publish` from a workstation. Preserve Open BSV -License Version 6 through the repository's license synchronization and packed -artifact controls. - -## Required verification - -Run from the repository root: - -```sh -pnpm --filter @bsv/paymail format:check -pnpm --filter @bsv/paymail lint -pnpm --filter @bsv/paymail typecheck -pnpm --filter @bsv/paymail test:coverage -pnpm --filter @bsv/paymail pack:check -pnpm --filter @bsv/paymail test:browser -pnpm --filter example-paymail format:check -pnpm --filter example-paymail lint -pnpm --filter example-paymail test -pnpm --filter example-paymail build -``` - -Coverage thresholds are enforced in `jest.config.js`. `pack:check` validates -the exact dry-packed tarball in clean ESM and CommonJS consumers. `test:browser` -validates the same tarball with Vite and esbuild against -`browser-budget.json`. Coverage collection must continue to include every -production file under `src`, including modules that no test imports. - -## Examples and secrets - -`docs/examples` is a private compiled fixture, not a deployable or published -package. External examples accept only operator-supplied -`ARC_API_KEY`, `PAYMAIL_EXAMPLE_SATOSHI_XPRV`, -`PAYMAIL_EXAMPLE_HAL_XPRV`, and `PAYMAIL_EXAMPLE_JWT_SECRET` environment -variables. Never commit credentials, private keys, live-user data, or fallback -secret values. `DOMAIN`, `PORT`, and `PAYMAIL_BASE_URL` configure the manual -server's advertised endpoint. +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/messaging/ts-paymail/CODE_OF_CONDUCT.md b/packages/messaging/ts-paymail/CODE_OF_CONDUCT.md deleted file mode 100644 index 194412a2c..000000000 --- a/packages/messaging/ts-paymail/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,30 +0,0 @@ -# Code of Conduct - -## Introduction -This Code of Conduct outlines the expectations for all contributors to our open-source project, as well as the responsibilities of the maintainers in ensuring a positive and productive environment. Our goal is to create excellent software. To achieve that, we foster a welcoming atmosphere for developers to collaborate and contribute to the project's success. - -## Expectations for Contributors -All contributors are expected to: -a. Treat others with respect and dignity. -b. Be considerate of differing viewpoints. -c. Collaborate constructively with others. -d. Refrain from demeaning or harassing others. - -## Reporting Violations -If you witness or experience any behavior that violates this Code of Conduct, please report it to the project maintainers. Reports can be made anonymously or by contacting a specific maintainer. - -## Enforcement -The project maintainers are responsible for enforcing this Code of Conduct. They will investigate any reported violations and take appropriate action, which may include: -a. Issuing a warning. -b. Requiring an apology or remediation. -c. Temporarily suspending or permanently banning a contributor from the project. - -## Maintainer Responsibilities -Maintainers are responsible for: -a. Upholding the Code of Conduct and setting a positive example for the community. -b. Investigating and addressing reported violations in a fair and timely manner. -c. Making decisions about merging code based on its merit alone, without bias or favoritism. - -## Changes to the Code of Conduct -This Code of Conduct is subject to change as the project evolves. Any updates will be communicated to all contributors and posted in the project repository. -By participating in this open-source project, you agree to abide by this Code of Conduct. We appreciate your cooperation in creating a positive and productive environment for everyone involved. diff --git a/packages/messaging/ts-paymail/CONTRIBUTING.md b/packages/messaging/ts-paymail/CONTRIBUTING.md deleted file mode 100644 index 78f63394f..000000000 --- a/packages/messaging/ts-paymail/CONTRIBUTING.md +++ /dev/null @@ -1,85 +0,0 @@ -# CONTRIBUTING - -Thank you for considering contributing in the BSV Blockchain ecosystem! This document outlines the processes and practices we expect contributors to adhere to. - -## Table of Contents - -1. [General Guidelines](#general-guidelines) -2. [Code of Conduct](#code-of-conduct) -3. [Getting Started](#getting-started) -4. [Pull Request Process](#pull-request-process) -5. [Coding Conventions](#coding-conventions) -6. [Documentation and Testing](#documentation-and-testing) -7. [Contact & Support](#contact--support) - -## General Guidelines - -- **Issues First**: If you're planning to add a new feature or change existing behavior, please open an issue first. This allows us to avoid multiple people working on similar features and provides a place for discussion. - -- **Stay Updated**: Always pull the latest changes from the main branch before creating a new branch or starting on new code. - -- **Simplicity Over Complexity**: Your solution should be as simple as possible, given the requirements. - -## Code of Conduct - -### Posting Issues and Comments - -- **Be Respectful**: Everyone is here to help and grow. Avoid any language that might be considered rude or offensive. - -- **Be Clear and Concise**: Always be clear about what you're suggesting or reporting. If an issue is related to a particular piece of code or a specific error message, include that in your comment. - -- **Stay On Topic**: Keep the conversation relevant to the issue at hand. If you have a new idea or unrelated question, please open a new issue. - -### Coding and PRs - -- **Stay Professional**: Avoid including "fun" code, comments, or irrelevant file changes in your commits and pull requests. - -## Getting Started - -1. **Fork the Repository**: Click on the "Fork" button at the top-right corner of this repository. - -2. **Clone the Forked Repository**: `git clone https://github.com/YOUR_USERNAME/PROJECT.git` - -3. **Navigate to the Directory**: `cd bsv-sdk` - -4. **Install Dependencies**: Always run `npm install` after pulling to ensure tooling is up to date. - -## Pull Request Process - -1. **Create a Branch**: For every new feature or bugfix, create a new branch. - -2. **Commit Your Changes**: Make your changes and commit them. Commit messages should be clear and concise to explain what was done. - -3. **Run Tests**: Ensure all tests pass using Jest: `npm test`. - -4. **Documentation**: All code must be fully annotated with comments. Update the documentation by running `npm run doc` before creating a pull request. - -5. **Push to Your Fork**: `git push origin your-new-branch`. - -6. **Open a Pull Request**: Go to your fork on GitHub and click "New Pull Request". Fill out the PR template, explaining your changes. - -7. **Code Review**: At least two maintainers must review and approve the PR before it's merged. Address any feedback or changes requested. - -8. **Merge**: Once approved, the PR will be merged into the main branch. - -## Coding Conventions - -> ADAPT TO YOUR PROJECT - -- **Code Style**: We use `Oxlint` for our TypeScript coding style. Run `npm run lint` to ensure your code adheres to this style. - -- **Testing**: Always include tests for new code or changes. We aim for industry-standard levels of test coverage. - -- **Documentation**: All functions, classes, and modules should be documented. Use annotation comments to describe the purpose, parameters, and return values. - -## Documentation and Testing - -- **Documentation**: Update the documentation whenever you add or modify the code. Run `npm run doc` to generate the latest docs. - -- **Testing**: We use Jest for all tests. Write comprehensive tests, ensuring edge cases are covered. All PRs should maintain or improve the current test coverage. - -## Contact & Support - -If you have any questions or need assistance with your contributions, feel free to reach out. Remember, we're here to help each other grow and improve the ecosystem. - -Thank you for being a part of this journey. Your contributions help shape the future of the BSV Blockchain! diff --git a/packages/messaging/ts-paymail/docs/examples/AGENTS.md b/packages/messaging/ts-paymail/docs/examples/AGENTS.md new file mode 100644 index 000000000..7fcd1093d --- /dev/null +++ b/packages/messaging/ts-paymail/docs/examples/AGENTS.md @@ -0,0 +1,10 @@ +# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](../../../../../AGENTS.md) +and [contribution policy](../../../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/middleware/402-pay/.github/workflows/push.yaml b/packages/middleware/402-pay/.github/workflows/push.yaml deleted file mode 100644 index f42182262..000000000 --- a/packages/middleware/402-pay/.github/workflows/push.yaml +++ /dev/null @@ -1,75 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/middleware/402-pay/AGENTS.md b/packages/middleware/402-pay/AGENTS.md index d0a6de3c1..dcea67c80 100644 --- a/packages/middleware/402-pay/AGENTS.md +++ b/packages/middleware/402-pay/AGENTS.md @@ -1,179 +1,10 @@ -# CLAUDE.md — @bsv/402-pay +# ts-stack agent instructions -## Purpose (1-2 sentences) +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -BRC-121 HTTP 402 Payment Required handler for client and server. Client-side: auto-pays 402 responses. Server-side: middleware/validation for accepting BSV micropayments over HTTP. - -## Public API surface - -### Server (@bsv/402-pay/server) -- `createPaymentMiddleware(opts: PaymentMiddlewareOptions): Express middleware` — Express middleware for automatic 402 handling - - `opts.wallet: WalletInterface` — Wallet to accept payment into - - `opts.calculatePrice(path: string): number | undefined` — Return satoshi price or undefined to skip payment - - Sets `req.payment` on success -- `validatePayment(req: PaymentRequest, wallet: WalletInterface, requiredSats: number, paymentWindowMs?: number): Promise` — Validate incoming payment headers -- `send402(res: Response, serverIdentityKey: string, satoshis: number): void` — Send 402 response with payment request headers - -### Client (@bsv/402-pay/client) -- `create402Fetch(opts: Payment402Options): typeof fetch` — Fetch wrapper that auto-handles 402 responses - - `opts.wallet: WalletInterface` — Wallet to pay with - - `opts.cacheTimeoutMs?: number` — Cache paid content (default: 30 min) - - Returns fetch-compatible function with `.clearCache()` method -- `constructPaymentHeaders(wallet: WalletInterface, url: string, satoshis: number, serverIdentityKey: string): Promise` — Build payment headers manually - -### Types -- `PaymentMiddlewareOptions` — Middleware configuration -- `PaymentResult` — Accepted payment with `satoshisPaid`, `senderIdentityKey`, and `txid` -- `PaymentError` — Explicit replay rejection with a safe reason -- `PaymentHeaders` — Five required headers { x-bsv-beef, x-bsv-sender, x-bsv-nonce, x-bsv-time, x-bsv-vout } -- `Payment402Options` — Client configuration -- `PaymentRequest` — Server's 402 request -- `PaymentResponse` — Payment validation response - -### Constants & Exports -- `HEADERS` — Header names enum (BEEF, SENDER, NONCE, TIME, VOUT) -- `BRC29_PROTOCOL_ID` — Protocol ID for payment key derivation -- `DEFAULT_PAYMENT_WINDOW_MS` — Timestamp freshness window (30 seconds) - -## Real usage patterns - -```typescript -// ===== SERVER SIDE ===== -import express from 'express' -import { createPaymentMiddleware } from '@bsv/402-pay/server' - -const app = express() - -app.use('/articles/:slug', createPaymentMiddleware({ - wallet, // WalletInterface - calculatePrice: (path) => { - if (path.includes('/premium/')) return 1000 // 1000 sats - return undefined // Free content - } -})) - -app.get('/articles/:slug', (req, res) => { - // req.payment is set if payment was accepted - if (req.payment) { - res.json({ article: 'Paid content here', paidBy: req.payment.senderIdentityKey }) - } else { - res.json({ article: 'Free content' }) - } -}) - -// Or low-level: -import { validatePayment, send402 } from '@bsv/402-pay/server' - -app.get('/premium', async (req, res) => { - const requiredSatoshis = 100 - const result = await validatePayment(req, wallet, requiredSatoshis) - if (!result) { - send402(res, serverIdentityKey, requiredSatoshis) - return - } - if (!result.accepted) { - send402(res, serverIdentityKey, requiredSatoshis) - return - } - res.json({ content: 'Premium stuff', tx: result.txid }) -}) - -// ===== CLIENT SIDE ===== -import { create402Fetch } from '@bsv/402-pay/client' - -const fetch402 = create402Fetch({ - wallet, // WalletClient - cacheTimeoutMs: 30 * 60 * 1000 // Cache for 30 minutes -}) - -// Automatically handles 402 with payment -const response = await fetch402('https://example.com/articles/foo') -const article = await response.json() -console.log(article) - -// Clear cache between sessions -fetch402.clearCache() - -// Or construct headers manually for custom fetch: -import { constructPaymentHeaders } from '@bsv/402-pay/client' - -const headers = await constructPaymentHeaders( - wallet, - 'https://example.com/articles/foo', - 100, // 100 sats - serverPublicKey -) - -const res = await fetch('https://example.com/articles/foo', { headers }) -``` - -## Key concepts - -- **402 Payment Required** — HTTP status code for micropayments (BRC-121) -- **Server Headers (402 response):** - - `x-bsv-sats` — Satoshi amount required - - `x-bsv-server` — Server identity public key (for key derivation) -- **Client Headers (payment request):** - - `x-bsv-beef` — Base64-encoded transaction proof - - `x-bsv-sender` — Client identity public key - - `x-bsv-nonce` — Derivation prefix (random 8 bytes, Base64) - - `x-bsv-time` — Unix millisecond timestamp - - `x-bsv-vout` — Output index (usually 0) -- **Replay Protection:** - 1. Timestamp freshness — `x-bsv-time` must be within 30 seconds of server - 2. Transaction uniqueness — `internalizeAction` returns `isMerge: true` for duplicates -- **Key Derivation** — Client derives payment pubkey via BRC-29 using server's identity key + nonce + timestamp -- **Caching** — Client can cache successful paid GET content per URL to avoid re-payment within timeout -- **P2PKH Script** — Payment is always P2PKH: `OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG` - -## Dependencies - -**Runtime:** -- `@bsv/sdk` ^2.1.6 (PublicKey, Utils, Random, WalletInterface) - -**Peer:** -- `@bsv/sdk` (peer dependency; application must provide) - -**Dev:** -- TypeScript, Vitest with V8 coverage, oxlint, tsdown, @types/node - -## Common pitfalls / gotchas - -1. **Timestamp must be fresh** — If `x-bsv-time` is >30 seconds old, payment is rejected -2. **Server identity key required** — Server must send its identity public key in 402 response; client uses it for key derivation -3. **Transaction uniqueness** — Replaying same BEEF twice is rejected; nonce must be different -4. **Cache timeout too long** — If cached, same content is served without payment check; short timeout (e.g., 30 min) recommended -5. **BEEF must be valid** — Malformed or invalid BEEF transaction is rejected during validation -6. **Wallet not available** — Client-side `create402Fetch` requires working WalletClient; fails if wallet is not installed/running -7. **calculatePrice undefined** — If price calculator returns undefined, payment is skipped (content is free) -8. **Server clock skew** — If server and client clocks differ by >30 seconds, payment fails; use NTP -9. **Browser CORS** — The service owns CORS policy. Expose `x-bsv-sats` and `x-bsv-server` and allow the five client payment headers for trusted/configured origins; the package does not impose an origin allowlist. - -## Spec conformance - -- **BRC-121** — HTTP 402 Payment Required micropayments -- **BRC-29** — Key derivation (payment pubkey via nonce + timestamp) -- **BRC-42** — Public key derivation -- **BRC-100** — Wallet interface -- **P2PKH** — Standard Bitcoin SV locking script -- **BEEF** — Transaction proof format - -## File map - -``` -402-pay/ - src/ - index.ts # Main exports (all modules) - server.ts # createPaymentMiddleware, validatePayment, send402 - client.ts # create402Fetch, constructPaymentHeaders - constants.ts # HEADERS, BRC29_PROTOCOL_ID, DEFAULT_PAYMENT_WINDOW_MS - server.test.ts # Server tests - client.test.ts # Client tests - constants.test.ts # Constants tests -``` - -## Integration points - -- **Depends on:** `@bsv/sdk` (WalletClient, WalletInterface, PublicKey, Utils) -- **Used by:** HTTP servers and clients implementing micropayments (paywalls, API gateways, content platforms) -- **Complements:** `@bsv/simple` (wallet instance for payments), any HTTP framework (Express, Hono, etc.) +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/middleware/auth-express-middleware/.github/workflows/push.yaml b/packages/middleware/auth-express-middleware/.github/workflows/push.yaml deleted file mode 100644 index c93f87e49..000000000 --- a/packages/middleware/auth-express-middleware/.github/workflows/push.yaml +++ /dev/null @@ -1,73 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/middleware/auth-express-middleware/AGENTS.md b/packages/middleware/auth-express-middleware/AGENTS.md index 9303979c5..dcea67c80 100644 --- a/packages/middleware/auth-express-middleware/AGENTS.md +++ b/packages/middleware/auth-express-middleware/AGENTS.md @@ -1,91 +1,10 @@ -# AGENTS.md — @bsv/auth-express-middleware +# ts-stack agent instructions -## Purpose and protocol boundary +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -This package is the Express transport for BRC-103 peer-to-peer mutual -authentication over BRC-104 HTTP. It owns handshake dispatch, authenticated -request conversion, signed-response buffering, optional certificate exchange, -and bounded pending transport state. Authorization remains an application -responsibility. - -`/.well-known/auth` must remain intentionally reachable so a session can be -established. Do not broaden that exception to path prefixes. - -## Public API - -- Runtime: `createAuthMiddleware`, `ExpressTransport` -- Types: `AuthMiddlewareOptions`, `AuthRequest`, `AuthTransportLimits`, - `LogLevel` -- Compatibility: `ExpressTransport.allowAuthenticated` is a deprecated alias - for `allowUnauthenticated`. - -Changing exports, response error codes, signed field ordering, header -normalization, or response wrapping is a public/protocol change. - -## Security invariants - -- Fail closed unless `allowUnauthenticated` is explicitly true. -- Validate attacker-controlled headers and handshake bodies before allocating - protocol state. -- Keep every pending listener/handle bounded by `requestTimeoutMs` and - `maxPendingRequests`; clean timers and SDK listeners on every terminal path. -- Reject simultaneously duplicated request IDs without blocking legitimate - later handshake phases. -- Do not log auth headers, signatures, certificates, request/response bodies, - wallet instances, or complete peer objects. Log metadata and stable IDs only. -- Do not return internal wallet, signing, storage, or certificate-handler error - messages. -- Use a shared `AsyncSessionManager` for horizontally scaled services. -- Use HTTPS; authentication does not provide transport confidentiality. -- Do not impose a hard-coded CORS/CSP policy. Public services must remain - configurable and may be cross-origin by default. - -## Express behavior - -- Body parsing runs before auth so the signed representation matches the route - input. -- Install one auth middleware instance per request path; authenticated - responses temporarily wrap Express response methods. -- Keep public errors stable and test status/code/description, not internal - exception text. -- If adding support for another response method, test buffering, signing, - restoration, failures, and duplicate-send behavior. - -## Build and verification - -Node.js 22+ is required. `tsdown` emits native ESM, CommonJS, and matching -declarations. The published file allowlist is `dist`, `README.md`, and -`LICENSE.txt`. - -Before handing off a change, run: - -```bash -pnpm typecheck -pnpm lint -pnpm format:check -pnpm test:coverage -pnpm pack:check -``` - -Coverage must remain at least 85% lines/statements/functions and 80% branches -over production source. Tests must be deterministic and must not rebuild as a -side effect. `pack:check` must validate the exact tarball in both ESM and -CommonJS consumer probes. - -## File map - -- `mod.ts` — package entry point and export contract -- `src/index.ts` — transport and middleware factory -- `src/authMiddlewareHelpers.ts` — deterministic request/response encoding -- `src/__tests/` — unit, hardening, and local integration coverage -- `tsdown.config.ts` — ESM/CommonJS build -- `tsconfig.typecheck.json` — strict source and test checking -- `README.md` — user contract and deployment guidance -- `BASELINE.md` — verified repository health snapshot - -## Integration points - -- `@bsv/sdk` supplies `Peer`, session managers, protocol types, wallet - interfaces, and `AuthFetch`. -- `@bsv/payment-express-middleware` must run after this package. -- `@bsv/authsocket` is the corresponding WebSocket path. +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/middleware/auth/AGENTS.md b/packages/middleware/auth/AGENTS.md new file mode 100644 index 000000000..dcea67c80 --- /dev/null +++ b/packages/middleware/auth/AGENTS.md @@ -0,0 +1,10 @@ +# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/middleware/payment-express-middleware/.github/workflows/push.yaml b/packages/middleware/payment-express-middleware/.github/workflows/push.yaml deleted file mode 100644 index 8de33b16d..000000000 --- a/packages/middleware/payment-express-middleware/.github/workflows/push.yaml +++ /dev/null @@ -1,73 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/middleware/payment-express-middleware/AGENTS.md b/packages/middleware/payment-express-middleware/AGENTS.md index 62f5e19c7..dcea67c80 100644 --- a/packages/middleware/payment-express-middleware/AGENTS.md +++ b/packages/middleware/payment-express-middleware/AGENTS.md @@ -1,85 +1,10 @@ -# AGENTS.md — @bsv/payment-express-middleware +# ts-stack agent instructions -## Purpose and protocol boundary +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -This package implements the legacy authenticated `x-bsv-payment` JSON flow for -Express. It must run after `@bsv/auth-express-middleware`. It is distinct from -the BRC-121 client/server contract in `@bsv/402-pay`; do not silently mix their -headers or claim conformance to one based on behavior from the other. - -## Public API - -- Runtime: `createPaymentMiddleware`, `InMemoryPaymentReplayStore` -- Types: `BSVPayment`, `PaymentLogger`, `PaymentMiddlewareOptions`, - `PaymentReceipt`, `PaymentReplayStore`, `PaymentRequest` - -Changing header names, error codes, output selection, replay semantics, receipt -fields, or exported types is a public/protocol change. - -## Security invariants - -- Require a valid compressed identity key supplied by auth middleware. -- Prices are `0` or positive safe integers. Invalid or failed pricing must not - authorize a route. -- Bound the raw header before JSON or transaction parsing. -- Require canonical base64 for the prefix, suffix, and Atomic BEEF. -- Verify the server-created prefix, parse Atomic BEEF, and require output zero - to cover the current price before wallet internalization. -- Claim the transaction ID atomically before calling the wallet. -- Never automatically release a claim after an ambiguous wallet error. -- Only `{ accepted: true }` from a newly internalized transaction authorizes - the request. Reject merge/replay-like results. -- Never expose wallet, store, pricing, or parser exception messages in HTTP - responses. -- Use a shared durable replay store for multiple processes or replicas. A - derivation nonce is not an expiring single-use replay store. -- Do not impose a hard-coded CORS/CSP policy. Public services must remain - configurable and may be cross-origin by default. - -## Replay-store contract - -`claim(transactionId)` must be one atomic insert-if-absent operation. It returns -`true` only for the first accepted claim and `false` thereafter. - -The in-memory implementation is process-local, bounded, and fail-closed at -capacity. It is for tests and bounded single-process deployments. Do not add -automatic eviction or restart-based assumptions to its security model. - -## Build and verification - -Node.js 22+ is required. `tsdown` emits native ESM, CommonJS, and matching -declarations. The published file allowlist is `dist`, `README.md`, and -`LICENSE.txt`. - -Before handing off a change, run: - -```bash -pnpm typecheck -pnpm lint -pnpm format:check -pnpm test:coverage -pnpm pack:check -``` - -Coverage must remain at least 85% lines/statements/functions and 80% branches -over production source. Tests must be deterministic and must not call public -APIs or rebuild as a side effect. `pack:check` must validate the exact tarball -in both ESM and CommonJS consumer probes. - -## File map - -- `mod.ts` — package entry point and export contract -- `src/index.ts` — validation, replay claim, wallet acceptance, middleware -- `src/types.ts` — public request, receipt, store, logger, and option types -- `src/__tests/PaymentMiddleware.test.ts` — deterministic security and behavior - coverage -- `tsdown.config.ts` — ESM/CommonJS build -- `tsconfig.typecheck.json` — strict source and test checking -- `README.md` — user contract and deployment guidance -- `BASELINE.md` — verified repository health snapshot - -## Integration points - -- `@bsv/auth-express-middleware` provides authenticated payer identity. -- `@bsv/sdk` supplies nonce utilities, Atomic BEEF parsing, and wallet types. -- Express provides the request/response middleware lifecycle. +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/network/ts-p2p/.github/ISSUE_TEMPLATE/bug_report.md b/packages/network/ts-p2p/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 4fc56f13f..000000000 --- a/packages/network/ts-p2p/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: Bug Report -about: Report a bug or an issue you've found with `@bsv/sdk`. -title: "[BUG]" -labels: bug -assignees: '' - ---- - -## Bug Description - -Briefly describe the bug/issue you've encountered. - -## Steps to Reproduce - -1. Step 1 -2. Step 2 -3. ... - -## Expected Behavior - -What should have happened if the bug hadn't occurred? - -## Actual Behavior - -What actually happened? - -## Stack Traces or Screenshots - -If applicable, add screenshots or stack traces to help explain the issue. - -## Environment - -- OS: [e.g. MacOS, Windows] -- Node version: [e.g. 12.20] -- `@bsv/sdk` version: [e.g. 1.2.3] - -## Additional Information - -Provide any additional context or information about the bug. diff --git a/packages/network/ts-p2p/.github/ISSUE_TEMPLATE/discussion.md b/packages/network/ts-p2p/.github/ISSUE_TEMPLATE/discussion.md deleted file mode 100644 index 52aa083ce..000000000 --- a/packages/network/ts-p2p/.github/ISSUE_TEMPLATE/discussion.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Discussion -about: Propose a discussion or seek clarification about a feature or topic. -title: "[DISCUSSION]" -labels: discussion -assignees: '' - ---- - -## Summary - -Briefly describe the topic you'd like to discuss. - -## Motivation - -Why do you believe this to be important? - -## Description - -Provide a detailed description or elaborate on your topic. - -## Additional References - -Provide any additional articles, links, or context that would help facilitate the discussion. diff --git a/packages/network/ts-p2p/.github/pull_request_template.md b/packages/network/ts-p2p/.github/pull_request_template.md deleted file mode 100644 index 448358cfe..000000000 --- a/packages/network/ts-p2p/.github/pull_request_template.md +++ /dev/null @@ -1,23 +0,0 @@ -## Description of Changes - -Provide a brief description of the changes you've made. - -## Linked Issues / Tickets - -Reference any related issues or tickets, e.g. "Closes #123". - -## Testing Procedure - -Describe the tests you've added or any testing steps you've taken. - -- [ ] I have added new unit tests -- [ ] All tests pass locally -- [ ] I have tested manually in my local environment - -## Checklist: - -- [ ] I have performed a self-review of my own code -- [ ] I have made corresponding changes to the documentation -- [ ] My changes generate no new warnings -- [ ] I have updated `CHANGELOG.md` with my changes -- [ ] I have run the linter \ No newline at end of file diff --git a/packages/network/ts-p2p/.github/workflows/push.yaml b/packages/network/ts-p2p/.github/workflows/push.yaml deleted file mode 100644 index c93f87e49..000000000 --- a/packages/network/ts-p2p/.github/workflows/push.yaml +++ /dev/null @@ -1,73 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/network/ts-p2p/AGENTS.md b/packages/network/ts-p2p/AGENTS.md index 7ec44ed83..dcea67c80 100644 --- a/packages/network/ts-p2p/AGENTS.md +++ b/packages/network/ts-p2p/AGENTS.md @@ -1,173 +1,10 @@ -# CLAUDE.md — @bsv/teranode-listener +# ts-stack agent instructions -## Purpose -TypeScript library for subscribing to Teranode P2P topics (blocks, subtrees, mining updates, etc.) via libp2p private DHT network. Enables applications to listen to Bitcoin SV blockchain events in real-time from a Teranode peer. +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -## Public API surface - -### TeranodeListener (Class — Recommended) -- **Constructor**: `new TeranodeListener(topicCallbacks, config?)` - - `topicCallbacks: Partial>` — object mapping topic names to callbacks - - `config?: TeranodeListenerConfig` — optional connection config (uses mainnet defaults if omitted) - - Does not start automatically; call `await start()` after construction - -- **Methods**: - - `async start()` — start listener and connect to Teranode peers (required after construction) - - `async stop()` — cleanly shutdown listener - - `addTopicCallback(topic, callback)` — dynamically add/subscribe to a topic - - `removeTopicCallback(topic)` — unsubscribe from a topic - - `getNode()` — get underlying libp2p `Libp2p` instance - - `getConnectedPeerCount()` — return number of connected peers - -### startSubscriber (Function — Legacy API) -- **Function**: `async startSubscriber(config?: SubscriberConfig)` - - Options: `bootstrapPeers`, `staticPeers`, `sharedKey`, `dhtProtocolID`, `topics`, `listenAddresses`, `usePrivateDHT` - - Returns `Promise` — listener runs in background; call process termination to stop - -## Real usage patterns - -From README (TeranodeListener — recommended): -```ts -import { TeranodeListener } from '@bsv/teranode-listener' - -// Define callbacks for topics -const blockCallback = (data: Uint8Array, topic: string, from: string) => { - console.log(`New block from ${from}:`, data) -} - -const subtreeCallback = (data: Uint8Array, topic: string, from: string) => { - console.log(`Subtree update from ${from}:`, data) -} - -// Create listener with callbacks -const listener = new TeranodeListener({ - 'bitcoin/mainnet-block': blockCallback, - 'bitcoin/mainnet-subtree': subtreeCallback -}) - -await listener.start() -console.log('Listener started, waiting for messages...') -``` - -Function-based API (legacy): -```ts -import { startSubscriber } from '@bsv/teranode-listener' - -// Start with default config (mainnet) -const { node, stop } = await startSubscriber({ - onMessage: (data, topic, from) => { - console.log(`Message on ${topic} from ${from}:`, data) - } -}) - -console.log('Subscriber started...') -``` - -Custom configuration: -```ts -const listener = new TeranodeListener( - { - 'teranode/blocks': (data, topic, from) => { - console.log('Block received:', data) - } - }, - { - topics: ['teranode/blocks'], - listenAddresses: ['/ip4/0.0.0.0/tcp/4000'], - dhtProtocolID: '/custom-protocol' - } -) - -await listener.start() -``` - -Dynamic topic subscription: -```ts -const listener = new TeranodeListener({ - 'bitcoin/mainnet-block': (data, topic, from) => { - console.log('Block:', data) - } -}) - -await listener.start() - -// Later, add more topics -listener.addTopicCallback('bitcoin/mainnet-subtree', (data, topic, from) => { - console.log('Subtree:', data) -}) - -// Unsubscribe from a topic -listener.removeTopicCallback('bitcoin/mainnet-block') -``` - -## Key concepts - -- **Private DHT network**: Teranode uses a pre-shared key (PSK) to create a private, closed P2P network -- **libp2p gossipsub**: Pub/sub messaging layer for distributing blockchain events -- **Topic-based subscriptions**: Subscribe to specific Teranode topics (e.g., 'bitcoin/mainnet-block') -- **Bootstrap peers**: Known peers to connect to initially; discover more peers from there -- **Static peers**: Explicitly configured peers to maintain connections to -- **Peer discovery**: libp2p discovers peers via bootstrap and pubsub peer discovery protocol -- **Message callbacks**: Each topic can have its own callback for async message handling -- **Graceful shutdown**: Call `.stop()` or catch SIGINT for clean connection teardown - -## Topic Types - -- `bitcoin/mainnet-bestblock` — Best block message -- `bitcoin/mainnet-block` — Block solution found -- `bitcoin/mainnet-subtree` — Subtree created -- `bitcoin/mainnet-mining_on` — Mining enabled -- `bitcoin/mainnet-handshake` — Peer connects -- `bitcoin/mainnet-rejected_tx` — Transaction rejected -- Similar topics exist for testnet: `bitcoin/testnet-*` - -## Dependencies - -- `@bsv/sdk` ^2.0.14 — SDK utilities -- `libp2p` ^2.9.0 — Base P2P networking library -- `@libp2p/tcp` ^10.1.18 — TCP transport -- `@chainsafe/libp2p-noise` ^16.1.4 — Encryption -- `@chainsafe/libp2p-yamux` ^7.0.4 — Stream multiplexing -- `@libp2p/kad-dht` ^15.1.10 — DHT protocol -- `@chainsafe/libp2p-gossipsub` ^14.1.1 — Gossip pub/sub -- `@libp2p/bootstrap` ^11.0.46 — Bootstrap peer discovery -- `@libp2p/pnet` ^2.0.42 — Private network support -- `@libp2p/pubsub-peer-discovery` ^11.0.0 — Pubsub-based peer discovery -- Dev: TypeScript, ts-node - -## Common pitfalls / gotchas - -1. **Node.js 22+ required** — This is the supported runtime floor declared by the package -2. **ES modules only** — Package is published as ESM; use `.mjs` files or set `"type": "module"` in package.json -3. **Teranode mainnet defaults** — If no config provided, connects to official Teranode mainnet; ensure correct `sharedKey` for private networks -4. **PSK hex format** — `sharedKey` must be hex-encoded; library auto-formats to PSK protocol -5. **Static peer connection** — If static peers are provided, library maintains reconnection; may spam logs if peers are unreachable -6. **Message data format** — Teranode messages are raw `Uint8Array`; caller must deserialize (typically BSV transaction or block data) -7. **No auto-reconnect between topics** — Once listener is started, adding/removing topics doesn't require restart -8. **Blocking callbacks** — If message callback is slow, other messages may queue; consider async processing - -## Spec conformance - -- **libp2p v2.9+** — Uses modern libp2p API -- **Gossipsub** — Standard pubsub protocol for message distribution -- **DHT (Kademlia)** — Distributed peer discovery -- **Noise protocol** — Modern encryption for libp2p connections -- **PSK (Pre-Shared Key)** — Private network isolation - -## File map - -``` -/Users/personal/git/ts-stack/packages/network/ts-p2p/ - src/ - index.ts — exports TeranodeListener, startSubscriber - README.md — documentation with examples - package.json — dependencies (libp2p, gossipsub, etc.) - tsconfig.json — TypeScript config -``` - -## Integration points - -- **@bsv/sdk** — Utilities and types -- **libp2p ecosystem** — All peer-to-peer networking via libp2p plugins -- **Teranode infrastructure** — Connects to official Teranode bootstrap and static peers -- No direct integration with other ts-stack packages; operates independently +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/network/ts-p2p/CODE_OF_CONDUCT.md b/packages/network/ts-p2p/CODE_OF_CONDUCT.md deleted file mode 100644 index 194412a2c..000000000 --- a/packages/network/ts-p2p/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,30 +0,0 @@ -# Code of Conduct - -## Introduction -This Code of Conduct outlines the expectations for all contributors to our open-source project, as well as the responsibilities of the maintainers in ensuring a positive and productive environment. Our goal is to create excellent software. To achieve that, we foster a welcoming atmosphere for developers to collaborate and contribute to the project's success. - -## Expectations for Contributors -All contributors are expected to: -a. Treat others with respect and dignity. -b. Be considerate of differing viewpoints. -c. Collaborate constructively with others. -d. Refrain from demeaning or harassing others. - -## Reporting Violations -If you witness or experience any behavior that violates this Code of Conduct, please report it to the project maintainers. Reports can be made anonymously or by contacting a specific maintainer. - -## Enforcement -The project maintainers are responsible for enforcing this Code of Conduct. They will investigate any reported violations and take appropriate action, which may include: -a. Issuing a warning. -b. Requiring an apology or remediation. -c. Temporarily suspending or permanently banning a contributor from the project. - -## Maintainer Responsibilities -Maintainers are responsible for: -a. Upholding the Code of Conduct and setting a positive example for the community. -b. Investigating and addressing reported violations in a fair and timely manner. -c. Making decisions about merging code based on its merit alone, without bias or favoritism. - -## Changes to the Code of Conduct -This Code of Conduct is subject to change as the project evolves. Any updates will be communicated to all contributors and posted in the project repository. -By participating in this open-source project, you agree to abide by this Code of Conduct. We appreciate your cooperation in creating a positive and productive environment for everyone involved. diff --git a/packages/network/ts-p2p/CONTRIBUTING.md b/packages/network/ts-p2p/CONTRIBUTING.md deleted file mode 100644 index 78f63394f..000000000 --- a/packages/network/ts-p2p/CONTRIBUTING.md +++ /dev/null @@ -1,85 +0,0 @@ -# CONTRIBUTING - -Thank you for considering contributing in the BSV Blockchain ecosystem! This document outlines the processes and practices we expect contributors to adhere to. - -## Table of Contents - -1. [General Guidelines](#general-guidelines) -2. [Code of Conduct](#code-of-conduct) -3. [Getting Started](#getting-started) -4. [Pull Request Process](#pull-request-process) -5. [Coding Conventions](#coding-conventions) -6. [Documentation and Testing](#documentation-and-testing) -7. [Contact & Support](#contact--support) - -## General Guidelines - -- **Issues First**: If you're planning to add a new feature or change existing behavior, please open an issue first. This allows us to avoid multiple people working on similar features and provides a place for discussion. - -- **Stay Updated**: Always pull the latest changes from the main branch before creating a new branch or starting on new code. - -- **Simplicity Over Complexity**: Your solution should be as simple as possible, given the requirements. - -## Code of Conduct - -### Posting Issues and Comments - -- **Be Respectful**: Everyone is here to help and grow. Avoid any language that might be considered rude or offensive. - -- **Be Clear and Concise**: Always be clear about what you're suggesting or reporting. If an issue is related to a particular piece of code or a specific error message, include that in your comment. - -- **Stay On Topic**: Keep the conversation relevant to the issue at hand. If you have a new idea or unrelated question, please open a new issue. - -### Coding and PRs - -- **Stay Professional**: Avoid including "fun" code, comments, or irrelevant file changes in your commits and pull requests. - -## Getting Started - -1. **Fork the Repository**: Click on the "Fork" button at the top-right corner of this repository. - -2. **Clone the Forked Repository**: `git clone https://github.com/YOUR_USERNAME/PROJECT.git` - -3. **Navigate to the Directory**: `cd bsv-sdk` - -4. **Install Dependencies**: Always run `npm install` after pulling to ensure tooling is up to date. - -## Pull Request Process - -1. **Create a Branch**: For every new feature or bugfix, create a new branch. - -2. **Commit Your Changes**: Make your changes and commit them. Commit messages should be clear and concise to explain what was done. - -3. **Run Tests**: Ensure all tests pass using Jest: `npm test`. - -4. **Documentation**: All code must be fully annotated with comments. Update the documentation by running `npm run doc` before creating a pull request. - -5. **Push to Your Fork**: `git push origin your-new-branch`. - -6. **Open a Pull Request**: Go to your fork on GitHub and click "New Pull Request". Fill out the PR template, explaining your changes. - -7. **Code Review**: At least two maintainers must review and approve the PR before it's merged. Address any feedback or changes requested. - -8. **Merge**: Once approved, the PR will be merged into the main branch. - -## Coding Conventions - -> ADAPT TO YOUR PROJECT - -- **Code Style**: We use `Oxlint` for our TypeScript coding style. Run `npm run lint` to ensure your code adheres to this style. - -- **Testing**: Always include tests for new code or changes. We aim for industry-standard levels of test coverage. - -- **Documentation**: All functions, classes, and modules should be documented. Use annotation comments to describe the purpose, parameters, and return values. - -## Documentation and Testing - -- **Documentation**: Update the documentation whenever you add or modify the code. Run `npm run doc` to generate the latest docs. - -- **Testing**: We use Jest for all tests. Write comprehensive tests, ensuring edge cases are covered. All PRs should maintain or improve the current test coverage. - -## Contact & Support - -If you have any questions or need assistance with your contributions, feel free to reach out. Remember, we're here to help each other grow and improve the ecosystem. - -Thank you for being a part of this journey. Your contributions help shape the future of the BSV Blockchain! diff --git a/packages/overlays/btms-backend/AGENTS.md b/packages/overlays/btms-backend/AGENTS.md index b13696a89..dcea67c80 100644 --- a/packages/overlays/btms-backend/AGENTS.md +++ b/packages/overlays/btms-backend/AGENTS.md @@ -1,157 +1,10 @@ -# CLAUDE.md — @bsv/btms-backend +# ts-stack agent instructions -## Purpose +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -BTMS Backend provides overlay services for the Basic Token Management System — a UTXO-based token protocol on BSV. Includes topic manager for token validation and lookup service for indexing/querying token state. Note: Core token definitions recently moved to `@bsv/overlay-topics/src/btms/`; this package may now be a thin shell or deprecated in favor of that location. - -## Public API surface - -From `mod.ts`: - -- **`BTMSTopicManager`** (default export) — Token validation and admission - - `identifyAdmissibleOutputs(beef, previousCoins)` → `Promise` - - `getDocumentation()` → `Promise` - - `getMetaData()` → `Promise<{name, shortDescription, version?, iconURL?, informationURL?}>` - -- **`BTMSLookupServiceFactory`** (default export from lookup-services) — Factory for lookup service - - Signature: `createBTMSLookupService(mongoDb: Db) → Promise` - -- **Types from `src/types.ts`**: - - `BTMSQuery` — Query shape for token lookups - - `BTMSRecord` — Token record in storage - - `BTMSLookupResult` — Result from lookup queries - -## Real usage patterns - -From README and tests: - -```typescript -// 1. Using BTMSTopicManager -import { BTMSTopicManager } from '@bsv/btms-backend' - -const topicManager = new BTMSTopicManager() - -// Validate a transaction -const result = await topicManager.identifyAdmissibleOutputs(beef, previousCoins) -// Returns: { outputsToAdmit: [0, 1], coinsToRetain: [] } - -// 2. Using BTMSLookupServiceFactory -import BTMSLookupServiceFactory from '@bsv/btms-backend' -import { MongoClient } from 'mongodb' - -const client = new MongoClient('mongodb://localhost:27017') -await client.connect() -const db = client.db('btms') - -const lookupService = await BTMSLookupServiceFactory(db) - -// 3. Querying tokens by asset ID -const results = await lookupService.lookup({ - service: 'ls_btms', - query: { assetId: 'txid.0' } -}) - -// 4. Querying tokens by owner public key -const ownerResults = await lookupService.lookup({ - service: 'ls_btms', - query: { ownerKey: '03abc123...' } -}) - -// 5. Registering in OverlayExpress -import OverlayExpress from '@bsv/overlay-express' -import { BTMSTopicManager, BTMSLookupServiceFactory } from '@bsv/btms-backend' - -const server = new OverlayExpress('mynode', privateKey, 'https://example.com') - -server.configureTopicManager('tm_btms', new BTMSTopicManager()) -await server.configureLookupServiceWithMongo('ls_btms', db => - BTMSLookupServiceFactory(db) -) - -await server.configureEngine() -await server.start() - -// 6. Direct access to topic and lookup documentation -const manager = new BTMSTopicManager() -const docs = await manager.getDocumentation() -const metadata = await manager.getMetaData() -// metadata = { name: 'btms', shortDescription: '...', version: '0.1.0', ... } -``` - -## Key concepts - -- **Token issuance**: Outputs with assetId = "ISSUE" create new tokens; subsequent transfers reference these via `txid.outputIndex` -- **Token protocol**: PushDrop-encoded with fields [assetIdField, amount, metadata?] - - assetIdField: "ISSUE" or "txid.outputIndex" reference - - amount: Numeric string representing token quantity - - metadata: Optional UTF-8 string (optional 3rd field) -- **Validation rules**: - 1. Issuance: assetId = "ISSUE" - 2. Transfer: Output amounts cannot exceed input amounts for same asset - 3. Metadata: Remains consistent across transfers if set during issuance - 4. Splitting: Tokens can be divided into multiple outputs - 5. Merging: Multiple tokens of same asset can be combined - 6. Burning: Tokens spent without corresponding outputs are destroyed -- **Lookup queries**: By assetId or ownerKey; auto-creates MongoDB indices -- **Storage**: MongoDB with auto-indexing on assetId, ownerKey, {txid, outputIndex} - -## Dependencies - -**Runtime:** -- `@bsv/overlay` — TopicManager, LookupService interfaces, AdmittanceInstructions -- `@bsv/sdk` — Transaction, LockingScript, PushDrop, PrivateKey, Utils -- `mongodb` — MongoDB driver (for lookup service) - -**Dev:** -- jest, ts-jest, typescript, Oxlint -- mongodb-memory-server — In-memory MongoDB for tests - -## Common pitfalls / gotchas - -1. **Asset ID semantics**: "ISSUE" = new token; any other value must match previous issuance txid.outputIndex -2. **Amount validation**: Must be numeric string and >= 1; non-numeric or negative amounts rejected -3. **Metadata detection**: If field 3 looks like a signature (binary, non-UTF8), it's treated as signature, not metadata -4. **Lookup factory is async**: `BTMSLookupServiceFactory()` returns a Promise; must await -5. **MongoDB required**: No Knex fallback; requires MongoDB for lookup service -6. **Conservation law**: Sum of input amounts per asset must >= sum of output amounts; violates rejected -7. **Field count**: Accepts 2-4 PushDrop fields ([assetId, amount, metadata?, signature?]) -8. **Deprecation note**: Core BTMS definitions now at `@bsv/overlay-topics/src/btms/`; this package may be shell or deprecated - -## Spec conformance - -- **BTMS Protocol**: Basic Token Management System (BSV token standard) -- **PushDrop encoding**: Uses PushDrop for locking script structure -- **Token conservation**: Enforces that total output amount <= total input amount per asset -- **Metadata persistence**: Optional metadata tracked across all token transfers - -## File map - -``` -src/ -├── topic-managers/ -│ ├── BTMSTopicManager.ts — Token validation logic -│ └── __tests__/ -│ └── BTMSTopicManager.test.ts -├── lookup-services/ -│ ├── BTMSLookupServiceFactory.ts — Factory for creating lookup service -│ ├── BTMSStorageManager.ts — MongoDB storage/indexing -│ ├── types.ts — Query/Record/Result types -│ ├── docs/ -│ │ ├── BTMSTopicManagerDocs.ts — Topic manager protocol docs -│ │ └── BTMSLookupDocs.ts — Lookup service protocol docs -│ └── __tests__/ -│ └── [Test files] -├── types.ts — Exported types (BTMSQuery, BTMSRecord, BTMSLookupResult) -└── docs/ - ├── BTMSTopicManagerDocs.ts — Protocol documentation - └── BTMSLookupDocs.ts — Lookup service documentation -mod.ts — Main exports -``` - -## Integration points - -- **@bsv/overlay**: Implements TopicManager/LookupService interfaces; Engine uses to validate/index tokens -- **@bsv/overlay-express**: Registered via `configureTopicManager('tm_btms', ...)` and `configureLookupServiceWithMongo('ls_btms', ...)` -- **@bsv/overlay-topics**: Core BTMS definitions and implementations now located here; this package may reference or wrap them -- **@bsv/sdk**: Uses PushDrop, Transaction, PrivateKey for token encoding/decoding -- **MongoDB**: Persistent storage for token UTXOs and query indices +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/overlays/gasp-core/.github/workflows/push.yaml b/packages/overlays/gasp-core/.github/workflows/push.yaml deleted file mode 100644 index c93f87e49..000000000 --- a/packages/overlays/gasp-core/.github/workflows/push.yaml +++ /dev/null @@ -1,73 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/overlays/gasp-core/AGENTS.md b/packages/overlays/gasp-core/AGENTS.md index 63b721fb7..dcea67c80 100644 --- a/packages/overlays/gasp-core/AGENTS.md +++ b/packages/overlays/gasp-core/AGENTS.md @@ -1,263 +1,10 @@ -# CLAUDE.md — @bsv/gasp +# ts-stack agent instructions -## Purpose +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -Graph Aware Sync Protocol (GASP) — a powerful protocol for synchronizing BSV transaction data between two or more parties. Enables incremental building of transaction ancestry/descendancy graphs, ensuring legitimacy via SPV, completeness through recursive input fetching, and efficiency by minimizing bandwidth. - -## Public API surface - -From `mod.ts` (re-exports from `src/GASP.ts`): - -**Main class:** - -- `GASP` — Orchestrator for graph-aware sync - - Constructor: `new GASP(storage, remote, lastInteraction?, logPrefix?, log?, unidirectional?, logLevel?, sequential?)` - - Methods: `sync()` → `Promise` - -**Interfaces:** - -- `GASPStorage` — Local database layer for UTXOs, transactions, metadata, graph management - - `findKnownUTXOs(since, limit?)` → `Promise` - - `hydrateGASPNode(graphID, txid, outputIndex, metadata)` → `Promise` - - `findNeededInputs(tx)` → `Promise` - - `appendToGraph(tx, spentBy?)` → `Promise` - - `validateGraphAnchor(graphID)` → `Promise` - - `discardGraph(graphID)` → `Promise` - - `finalizeGraph(graphID)` → `Promise` - -- `GASPRemote` — Communication with remote GASP peers - - `getInitialResponse(request)` → `Promise` - - `getInitialReply(response)` → `Promise` - - `requestNode(graphID, txid, outputIndex, metadata)` → `Promise` - - `submitNode(node)` → `Promise` - -**Types:** - -- `GASPInitialRequest` — { version, since, limit? } -- `GASPInitialResponse` — { UTXOList, since } -- `GASPInitialReply` — { UTXOList } -- `GASPOutput` — { txid, outputIndex, score } -- `GASPNode` — { graphID, rawTx, outputIndex, proof?, txMetadata?, outputMetadata?, inputs? } -- `GASPNodeResponse` — { requestedInputs: { txid.index: { metadata: boolean } } } - -**Enums/Constants:** - -- `LogLevel` — { ERROR, WARN, INFO, DEBUG } -- `GASPVersionMismatchError` — Custom error for version conflicts - -## Real usage patterns - -From README examples: - -```typescript -// 1. Implement GASPStorage -import { GASPStorage, GASPNode, GASPNodeResponse } from '@bsv/gasp' - -class MyCustomStorage implements GASPStorage { - async findKnownUTXOs(since: number, limit?: number) { - // Return array of unspent TXID-outputIndices since timestamp - return [ - { txid: 'abc...', outputIndex: 0, score: Date.now() }, - { txid: 'def...', outputIndex: 1, score: Date.now() } - ] - } - - async hydrateGASPNode(graphID, txid, outputIndex, metadata) { - // Return the GASPNode with rawTx, proof, metadata, etc. - return { - graphID, - rawTx: '0100...', // hex-encoded transaction - outputIndex, - proof: 'BUMP_proof...', // optional Merkle proof - txMetadata: 'custom_tx_data', - outputMetadata: 'custom_output_data' - } - } - - async findNeededInputs(tx: GASPNode): Promise { - // Optionally request more inputs if needed - if (tx.outputMetadata?.includes('magic')) { - return { - requestedInputs: { - 'some_txid.0': { metadata: true }, - 'some_txid.1': { metadata: false } - } - } - } - // If none needed, return undefined - } - - async appendToGraph(tx, spentBy?) { - // Store node in temporary graph structure - } - - async validateGraphAnchor(graphID) { - // Confirm graph is anchored in blockchain or otherwise valid - // Throw if validation fails - } - - async discardGraph(graphID) { - // Discard invalid graph - } - - async finalizeGraph(graphID) { - // Finalize validated graph into persistent storage - } -} - -// 2. Implement GASPRemote -import { GASPRemote, GASPInitialRequest, GASPInitialResponse } from '@bsv/gasp' - -class MyRemote implements GASPRemote { - async getInitialResponse(request: GASPInitialRequest) { - // Call remote peer via HTTP/WS and return their UTXO list - const response = await fetch('https://peer.example.com/gasp/initial', { - method: 'POST', - body: JSON.stringify(request) - }) - return response.json() - } - - async getInitialReply(response: GASPInitialResponse) { - // Bidirectional sync: get reply from remote - const reply = await fetch('https://peer.example.com/gasp/reply', { - method: 'POST', - body: JSON.stringify(response) - }) - return reply.json() - } - - async requestNode(graphID, txid, outputIndex, metadata) { - // Request a specific node from remote - const response = await fetch('https://peer.example.com/gasp/node', { - method: 'POST', - body: JSON.stringify({ graphID, txid, outputIndex, metadata }) - }) - return response.json() - } - - async submitNode(node) { - // Push node to remote; remote may request additional inputs - const response = await fetch('https://peer.example.com/gasp/submit', { - method: 'POST', - body: JSON.stringify(node) - }) - return response.json() - } -} - -// 3. Initialize and sync -import { GASP, LogLevel } from '@bsv/gasp' - -const myStorage = new MyCustomStorage() -const myRemote = new MyRemote() - -const gasp = new GASP( - myStorage, - myRemote, - 0, // lastInteraction (UNIX seconds) - '[GASP] ', // logPrefix - false, // legacy log toggle - false, // unidirectional? false = bidirectional - LogLevel.INFO, // logLevel - false // sequential? false = parallel operations -) - -await gasp.sync() -console.log('GASP sync complete!') - -// 4. Unidirectional (pull-only) sync -const gaspPullOnly = new GASP( - myStorage, - myRemote, - 0, - '[GASP-Pull] ', - false, - true, // unidirectional = true (pull only, no push) - LogLevel.DEBUG, - false -) - -await gaspPullOnly.sync() -// Local storage updated with remote UTXOs, but remote sees no data from us - -// 5. Sequential (DB-lock-safe) sync -const gaspSequential = new GASP( - myStorage, - myRemote, - 0, - '[GASP-Sequential] ', - false, - false, - LogLevel.WARN, - true // sequential = true (one operation at a time) -) - -await gaspSequential.sync() -``` - -## Key concepts - -- **Graph Aware Sync**: Unlike "UTXO list" sync, GASP recursively fetches transaction ancestors and descendants, building a validated transaction graph -- **Legitimacy**: Uses Merkle proofs (BUMP), script validation, and SPV rules to ensure only valid data is finalized -- **Completeness**: Recursively requests needed inputs until graph is complete or remote has no more data -- **Efficiency**: Only fetches data not already known; minimizes bandwidth by selective requests -- **Metadata support**: Each transaction/output can carry custom metadata (e.g., invoice data, topical membership); recursively propagated -- **Unidirectional mode**: "Pull only" from remote; useful for SPV clients that don't publish data -- **Sequential vs Parallel**: Parallel (default) uses Promise.all for speed; Sequential uses one-at-a-time to avoid DB locking -- **Graph ID**: Unique identifier for a transaction graph; typically TXID.outputIndex of the tip UTXO -- **BUMP proof**: Merkle proof anchoring a transaction in a block; optional but recommended for SPV validation - -## Dependencies - -**Runtime:** - -- `@bsv/sdk` — Transaction, utils for encoding/decoding - -**Dev:** - -- jest, ts-jest, typescript, Oxlint - -## Common pitfalls / gotchas - -1. **Storage method signatures**: All async; must be properly implemented to avoid data races -2. **Graph ID format**: Must be `txid.outputIndex` (36-byte format, colon-separated) -3. **findNeededInputs return**: Return `undefined` if no inputs needed; returning empty object `{}` may cause unexpected behavior -4. **Metadata recursion**: If you request metadata, inputs' metadata hashes are returned; use carefully to avoid bloat -5. **Version mismatch**: If remote runs different GASP version, sync fails with GASPVersionMismatchError -6. **Graph validation must throw**: If graph is invalid, `validateGraphAnchor()` must throw; returning gracefully doesn't stop sync -7. **Unidirectional limitation**: In unidirectional mode, don't call `submitNode()`; remote won't receive or process it -8. **Sequential overhead**: Sequential mode is slower; use only if parallel operations cause DB locking - -## Spec conformance - -- **Graph Aware Sync Protocol**: GASP version 1 (from mod.ts) -- **Merkle proof integration**: Supports BUMP (Merkle proof) for SPV validation -- **Transaction format**: Uses raw hex transaction encoding (rawTx field) -- **Metadata extensibility**: txMetadata and outputMetadata are arbitrary strings; protocols can define their own formats - -## File map - -``` -src/ -└── GASP.ts — Main GASP class and type definitions - ├── GASPInitialRequest - ├── GASPInitialResponse - ├── GASPInitialReply - ├── GASPOutput - ├── GASPNode - ├── GASPNodeResponse - ├── GASPStorage (interface) - ├── GASPRemote (interface) - ├── GASP (class) - ├── LogLevel (enum) - └── GASPVersionMismatchError -``` - -## Integration points - -- **@bsv/overlay Engine**: Uses OverlayGASPStorage and OverlayGASPRemote (adapters) to sync with other overlay services -- **@bsv/overlay-express**: Can configure GASP sync via `configureEnableGASPSync()`; disabled by default in development -- **Custom storage backends**: Implement GASPStorage for any persistence layer (SQL, NoSQL, files, etc.) -- **Custom remotes**: Implement GASPRemote for any transport (HTTP, WebSocket, in-process, etc.) -- **SHIP/SLAP discovery**: Can discover remote GASP peers via SHIP/SLAP before initiating sync +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/overlays/overlay-discovery-services/.github/workflows/push.yaml b/packages/overlays/overlay-discovery-services/.github/workflows/push.yaml deleted file mode 100644 index 41200dfdd..000000000 --- a/packages/overlays/overlay-discovery-services/.github/workflows/push.yaml +++ /dev/null @@ -1,72 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - -jobs: - test: - name: Build & Test - permissions: - contents: read - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write # Required for OIDC - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/overlays/overlay-discovery-services/AGENTS.md b/packages/overlays/overlay-discovery-services/AGENTS.md index 775842521..dcea67c80 100644 --- a/packages/overlays/overlay-discovery-services/AGENTS.md +++ b/packages/overlays/overlay-discovery-services/AGENTS.md @@ -1,164 +1,10 @@ -# CLAUDE.md — @bsv/overlay-discovery-services +# ts-stack agent instructions -## Purpose +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -Implements Service Host Interconnect Protocol (SHIP) and Service Lookup Availability Protocol (SLAP) for overlay service peer discovery. Also provides token-based advertiser implementations for certificate-based advertisements and wallet integration. - -## Public API surface - -From `mod.ts`: - -**SHIP (Service Host Interconnect Protocol):** -- `SHIPTopicManager` — Topic manager for SHIP advertisements -- `SHIPLookupService` — Lookup service for querying SHIP records -- `SHIPStorage` — Storage implementation for SHIP data - -**SLAP (Service Lookup Availability Protocol):** -- `SLAPTopicManager` — Topic manager for SLAP advertisements -- `SLAPLookupService` — Lookup service for querying SLAP records -- `SLAPStorage` — Storage implementation for SLAP data - -**Advertiser:** -- `WalletAdvertiser` — Token-based advertiser using certificates - -**Utilities:** -- `isAdvertisableURI(uri)` — Validates advertiser/host URIs -- `isValidTopicOrServiceName(name)` — Validates topic/service names (alphanumeric, hyphens, underscores) -- `isTokenSignatureCorrectlyLinked(...)` — Verifies token signature linkage - -**Types:** -- All exported from `src/types.ts` — SHIP/SLAP record structures, token types, signatures - -## Real usage patterns - -From test files and integration with Engine: - -```typescript -// 1. SHIP / SLAP are auto-registered by Engine -// When you create an Engine with shipTrackers and slapTrackers: -const engine = new Engine( - managers, - lookupServices, - storage, - chainTracker, - hostingURL, - ['https://ship.example.com'], // SHIP trackers - ['https://slap.example.com'], // SLAP trackers - broadcaster, - advertiser // WalletAdvertiser instance -) - -// 2. Query SHIP to discover overlay hosts -const shipResults = await engine.lookup({ - service: 'ls_ship', - query: { - topicName: 'tm_hello', - since: Date.now() - 86400000 // Last 24 hours - } -}) - -// 3. Query SLAP to discover lookup services -const slapResults = await engine.lookup({ - service: 'ls_slap', - query: { - serviceName: 'ls_hello', - since: Date.now() - 86400000 - } -}) - -// 4. Publish SHIP advertisement (via advertiser) -// The Engine handles this internally, but conceptually: -const advertiser = new WalletAdvertiser(wallet) -await advertiser.advertiseHost({ - uri: 'https://mynode.example.com', - topicName: 'tm_hello', - hostPublicKey: '03abc...' -}) - -// 5. Publish SLAP advertisement -await advertiser.advertiseService({ - uri: 'https://mynode.example.com', - serviceName: 'ls_hello', - servicePublicKey: '03def...' -}) - -// 6. Validate URIs and names -import { isAdvertisableURI, isValidTopicOrServiceName } from '@bsv/overlay-discovery-services' - -const valid = isAdvertisableURI('https://node.example.com') -const validName = isValidTopicOrServiceName('tm_custom_topic') -``` - -## Key concepts - -- **SHIP (Service Host Interconnect Protocol)**: Peer discovery for topic managers. Hosts advertise which topics they support. -- **SLAP (Service Lookup Availability Protocol)**: Peer discovery for lookup services. Nodes advertise which lookup services they provide. -- **Advertisement**: A transaction output containing metadata about a host/service (URI, public key, topic/service name) -- **Token-based**: Advertisements can be signed/verified using token signatures (certificates linked to transactions) -- **WalletAdvertiser**: Uses wallet to sign advertisements; verifies signatures are correctly linked to published outputs -- **Topic name format**: `tm_*` (e.g., `tm_hello`, `tm_ship`, `tm_btms`) -- **Service name format**: `ls_*` (e.g., `ls_hello`, `ls_slap`, `ls_btms`) -- **URI validation**: Must be HTTPS, valid domain, etc. -- **Signature verification**: Token signatures are verified against the transaction that created the advertisement - -## Dependencies - -**Runtime:** -- `@bsv/overlay` — TopicManager, LookupService interfaces -- `@bsv/sdk` — Transaction, PrivateKey, PublicKey, Signature, Utils -- `mongodb` — MongoDB for lookup service storage (optional, but expected for production) - -**Dev:** -- jest, ts-jest, typescript, Oxlint - -## Common pitfalls / gotchas - -1. **SHIP/SLAP auto-registration**: Don't manually add `tm_ship` and `tm_slap` unless you're building a custom engine; OverlayExpress handles this -2. **Topic/service naming**: Must follow pattern `tm_*` or `ls_*`; invalid names are rejected by validators -3. **URI format**: Must be valid HTTPS; localhost/IP addresses not advertised in production -4. **Token signature linkage**: WalletAdvertiser verifies that signature is linked to the transaction creating the advertisement; mismatched signatures fail -5. **Advertisement TTL**: SHIP/SLAP records have implicit expiration (tracked by `since` timestamp); stale records may be ignored -6. **Storage isolation**: SHIP and SLAP have separate storage; querying wrong service returns no results -7. **MongoDB indices**: SHIP/SLAP create indices on topicName/serviceName for efficient discovery; creation is automatic -8. **Peer bootstrapping**: Engine needs at least one SHIP/SLAP tracker URL to bootstrap peer discovery - -## Spec conformance - -- **SHIP protocol**: Service Host Interconnect Protocol (BSV overlay spec) -- **SLAP protocol**: Service Lookup Availability Protocol (BSV overlay spec) -- **Certificate linking**: Token signatures linked to outputs (per BSV wallet authentication standards) -- **PushDrop encoding**: Advertisement data stored in PushDrop format - -## File map - -``` -src/ -├── SHIP/ -│ ├── SHIPTopicManager.ts — Validates SHIP advertisement structure -│ ├── SHIPLookupService.ts — Queries SHIP records -│ ├── SHIPStorage.ts — MongoDB storage for SHIP -│ ├── SHIPTopic.docs.ts — Protocol documentation -│ └── SHIPLookup.docs.ts — Lookup documentation -├── SLAP/ -│ ├── SLAPTopicManager.ts — Validates SLAP advertisement structure -│ ├── SLAPLookupService.ts — Queries SLAP records -│ ├── SLAPStorage.ts — MongoDB storage for SLAP -│ ├── SLAPTopic.docs.ts — Protocol documentation -│ └── SLAPLookup.docs.ts — Lookup documentation -├── WalletAdvertiser.ts — Token-based advertiser implementation -├── types.ts — Shared types (SHIP/SLAP records, tokens, signatures) -├── utils/ -│ ├── isAdvertisableURI.ts — URI validation -│ ├── isValidTopicOrServiceName.ts — Name validation -│ └── isTokenSignatureCorrectlyLinked.ts — Signature verification -└── __tests__/ - └── [Test files for utilities and advertiser] -``` - -## Integration points - -- **@bsv/overlay Engine**: Auto-registers SHIP/SLAP topic managers and lookup services; uses Advertiser to broadcast -- **@bsv/overlay-express**: Provides `advertisableFQDN` and manages Advertiser instance for Engine -- **@bsv/gasp**: SHIP/SLAP can be sync partners; historical sync enabled via GASP for discovered peers -- **Custom advertisers**: Can implement alternative advertiser interfaces for different trust models -- **External SHIP/SLAP trackers**: Engine connects to bootstrap trackers; discovers peers dynamically +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/overlays/overlay-express/.github/ISSUE_TEMPLATE/bug_report.md b/packages/overlays/overlay-express/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index f72a3d310..000000000 --- a/packages/overlays/overlay-express/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: Bug Report -about: Report a bug or an issue you've found with `@bsv/identity-react`. -title: "[BUG]" -labels: bug -assignees: '' - ---- - -## Bug Description - -Briefly describe the bug/issue you've encountered. - -## Steps to Reproduce - -1. Step 1 -2. Step 2 -3. ... - -## Expected Behavior - -What should have happened if the bug hadn't occurred? - -## Actual Behavior - -What actually happened? - -## Stack Traces or Screenshots - -If applicable, add screenshots or stack traces to help explain the issue. - -## Environment - -- OS: [e.g. MacOS, Windows] -- Node version: [e.g. 12.20] -- `@bsv/identity-react` version: [e.g. 1.2.3] - -## Additional Information - -Provide any additional context or information about the bug. diff --git a/packages/overlays/overlay-express/.github/ISSUE_TEMPLATE/discussion.md b/packages/overlays/overlay-express/.github/ISSUE_TEMPLATE/discussion.md deleted file mode 100644 index 52aa083ce..000000000 --- a/packages/overlays/overlay-express/.github/ISSUE_TEMPLATE/discussion.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Discussion -about: Propose a discussion or seek clarification about a feature or topic. -title: "[DISCUSSION]" -labels: discussion -assignees: '' - ---- - -## Summary - -Briefly describe the topic you'd like to discuss. - -## Motivation - -Why do you believe this to be important? - -## Description - -Provide a detailed description or elaborate on your topic. - -## Additional References - -Provide any additional articles, links, or context that would help facilitate the discussion. diff --git a/packages/overlays/overlay-express/.github/pull_request_template.md b/packages/overlays/overlay-express/.github/pull_request_template.md deleted file mode 100644 index 035b61038..000000000 --- a/packages/overlays/overlay-express/.github/pull_request_template.md +++ /dev/null @@ -1,25 +0,0 @@ -## Description of Changes - -Provide a brief description of the changes you've made. - -## Linked Issues / Tickets - -Reference any related issues or tickets, e.g. "Closes #123". - -## Testing Procedure - -Describe the tests you've added or any testing steps you've taken. - -- [ ] I have added new unit tests -- [ ] All tests pass locally -- [ ] I have tested manually in my local environment - -## Checklist: - -- [ ] I have performed a self-review of my own code -- [ ] I have made corresponding changes to the documentation -- [ ] My changes generate no new warnings -- [ ] I have updated `CHANGELOG.md` with my changes -- [ ] I have run `npm run doc` and `npm run lint` one final time before requesting a review -- [ ] I have fixed all linter errors to ensure these changes are compliant with `ts-standard` -- [ ] I have run `npm version patch` so that my changes will trigger a new version to be released when they are merged \ No newline at end of file diff --git a/packages/overlays/overlay-express/.github/workflows/push.yaml b/packages/overlays/overlay-express/.github/workflows/push.yaml deleted file mode 100644 index 0c3aa8696..000000000 --- a/packages/overlays/overlay-express/.github/workflows/push.yaml +++ /dev/null @@ -1,75 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - tags: - - v* - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/overlays/overlay-express/AGENTS.md b/packages/overlays/overlay-express/AGENTS.md index e3da3d59d..dcea67c80 100644 --- a/packages/overlays/overlay-express/AGENTS.md +++ b/packages/overlays/overlay-express/AGENTS.md @@ -1,176 +1,10 @@ -# CLAUDE.md — @bsv/overlay-express +# ts-stack agent instructions -## Purpose +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -Opinionated Express.js HTTP server wrapper for @bsv/overlay. Simplifies deployment of overlay services with built-in configuration methods for Knex (SQL), MongoDB, topic managers, lookup services, GASP sync, Arc callbacks, and a responsive web UI. - -## Public API surface - -From `mod.ts`: - -- **`OverlayExpress`** (default export) — Main server class - - Constructor: `new OverlayExpress(name, privateKey, advertisableFQDN, adminToken?)` - - Config methods: - - `configurePort(port)` — Set HTTP listening port (default 3000) - - `configureKnex(knexUrl)` — Connect to SQL database - - `configureMongo(mongoUrl)` — Connect to MongoDB - - `configureNetwork(network)` — Set blockchain network ('main' | 'test', default 'main') - - `configureTopicManager(name, manager)` — Register a topic manager - - `configureLookupServiceWithMongo(name, factory)` — Create lookup service with MongoDB - - `configureLookupServiceWithKnex(name, factory)` — Create lookup service with Knex - - `configureEngine()` — Build underlying Engine - - `configureEnableGASPSync(enabled)` — Enable/disable GASP sync (default true) - - `configureEngineParams(config)` — Pass advanced Engine options - - `configureWebUI(config)` — Set web UI styling - - `configureJanitor(config)` — Configure health check janitor - - `configureHealth(config)` — Set health endpoint context - - `registerHealthCheck(check)` — Register custom health checks - - Lifecycle: `start()` starts Express; idempotent `close()` stops HTTP, - background synchronization, Knex, and MongoDB - - Admin: `getAdminToken()` — retrieve auto-generated or custom admin token - -- **`BanService`** — Manages blocked/banned outputs - - Constructor: `new BanService(mongoDb)` - - Methods: `ban(txid, outputIndex)`, `unban()`, `isBanned()`, `list()` - -- **`BanAwareLookupWrapper`** — Wraps lookup service to filter banned outputs - -- **`JanitorService`** — Health checks for SHIP/SLAP host availability - - Validates remote overlay hosts and revokes failing ones - -- **Types**: `EngineConfig`, `HealthCheckDefinition`, `HealthCheckHandler`, `HealthCheckResult`, `HealthConfig`, `HealthReport`, `HealthStatus` - -## Real usage patterns - -From README and tests: - -```typescript -// 1. Basic setup -const server = new OverlayExpress( - 'testnode', - process.env.SERVER_PRIVATE_KEY, - 'https://example.com' -) - -server.configurePort(8080) -await server.configureKnex(process.env.KNEX_URL) -await server.configureMongo(process.env.MONGO_URL) - -// 2. Register topic managers and lookup services -server.configureTopicManager('tm_hello', new HelloWorldTopicManager()) -await server.configureLookupServiceWithMongo('ls_hello', mongoDb => - createHelloWorldLookupService(mongoDb) -) - -// 3. Configure GASP and Arc -server.configureEnableGASPSync(true) -server.configureEngineParams({ - logTime: true, - throwOnBroadcastFailure: true -}) - -// 4. Configure web UI -server.configureWebUI({ - host: 'https://example.com', - primaryColor: '#ff0000' -}) - -// 5. Configure health endpoints with custom checks -server.configureHealth({ - contextProvider: async () => ({ - deployment: 'my-overlay', - network: 'main' - }) -}) - -server.registerHealthCheck({ - name: 'custom-cache', - critical: false, - handler: async () => ({ - status: 'ok', - details: { warmed: true } - }) -}) - -// 6. Start the server -await server.configureEngine() -await server.start() - -// 7. Admin token usage -const token = server.getAdminToken() -// Use as Bearer token: Authorization: Bearer -// For /admin/syncAdvertisements, /admin/startGASPSync endpoints -``` - -## Key concepts - -- **OverlayExpress**: One-stop configuration object; builds and starts an Express app with all overlay routes -- **Private key**: Identifies the overlay node; used for signing advertisements and transactions -- **Advertising FQDN**: Domain where this node is hosted; advertised in SHIP/SLAP for peer discovery -- **Admin token**: Bearer token for protected endpoints (/admin/syncAdvertisements, /admin/startGASPSync) -- **Knex vs MongoDB**: Knex for SQL (application-wide global storage), MongoDB for per-service indices -- **Health endpoints**: - - `/health/live` — Liveness (process running) - - `/health/ready` — Readiness (dependencies ready) - - `/health` — Full report with metadata and custom checks -- **JanitorService**: Periodically validates SHIP/SLAP hosts; revokes entries if health checks fail -- **BanService**: Optional banning of specific outputs (txid.outputIndex) -- **Web UI**: Auto-serves documentation and lets users explore overlay services - -## Dependencies - -**Runtime:** -- `@bsv/overlay` — Core engine and interfaces -- `@bsv/overlay-discovery-services` — SHIP/SLAP implementation -- `@bsv/sdk` — Transaction and wallet types -- `@bsv/auth-express-middleware` — Authentication for admin endpoints -- `@bsv/wallet-toolbox-client` — Wallet integration -- `express` — HTTP server -- `body-parser` — Request body parsing -- `mongodb` — MongoDB driver -- `knex` — SQL query builder -- `uuid` — ID generation -- `chalk` — Colored console output - -**Dev:** -- jest, ts-jest, typescript, Oxlint - -## Common pitfalls / gotchas - -1. **Initialization order**: `configureKnex` and `configureMongo` must complete before `configureEngine()` -2. **Private key format**: Must be valid BSV private key; used for signing, not storing secrets -3. **Admin token**: Auto-generated if not provided; store securely for production -4. **Health check criticality**: Mark as `critical: true` only for mandatory dependencies; failures block /health/ready -5. **Janitor timeout**: `requestTimeoutMs` must be reasonable; too short causes false revocations -6. **GASP sync overhead**: Disabling GASP sync (`configureEnableGASPSync(false)`) useful for local dev but loses peer sync -7. **Web UI paths**: Static assets served from `public/` directory if present; overlay service docs auto-generated -8. **BanService indexing**: Bans not persisted if underlying MongoDB goes down; transient storage only - -## Spec conformance - -- Implements BSV Overlay protocol with SHIP/SLAP peer discovery -- Supports Graph Aware Sync Protocol (GASP) for historical sync -- Optional Arc callback integration for proof-of-inclusion on mainnet -- Health endpoints follow Kubernetes liveness/readiness probe patterns - -## File map - -``` -src/ -├── OverlayExpress.ts — Main server configuration class -├── BanService.ts — Output banning logic -├── BanAwareLookupWrapper.ts — Lookup service wrapper that filters bans -├── JanitorService.ts — Health check and host revocation service -├── makeUserInterface.ts — Web UI generation -└── __tests__/ — Test files -``` - -## Integration points - -- **@bsv/overlay**: Provides Engine, TopicManager, LookupService interfaces -- **@bsv/overlay-topics**: Pre-built managers/services registered via configureTopicManager/configureLookupServiceWithMongo -- **@bsv/overlay-discovery-services**: SHIP/SLAP host discovery and peer advertisement -- **@bsv/gasp**: Optional Graph Aware Sync; configurable in engine params -- **Express**: All HTTP routing delegated to instance.app -- **Knex**: SQL database abstraction; migrations auto-applied -- **MongoDB**: Per-service lookup indices; optional but recommended for scalability +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/overlays/overlay/.github/ISSUE_TEMPLATE/bug_report.md b/packages/overlays/overlay/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 4619bd724..000000000 --- a/packages/overlays/overlay/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: Bug Report -about: Report a bug or an issue you've found with `@bsv/identity-react`. -title: "[BUG]" -labels: bug -assignees: '' - ---- - -## Bug Description - -Briefly describe the bug/issue you've encountered. - -## Steps to Reproduce - -1. Step 1 -2. Step 2 -3. ... - -## Expected Behavior - -What should have happened if the bug hadn't occurred? - -## Actual Behavior - -What actually happened? - -## Stack Traces or Screenshots - -If applicable, add screenshots or stack traces to help explain the issue. - -## Environment - -- OS: [e.g. MacOS, Windows] -- Node version: [e.g. 12.20] -- `@bsv/overlay` version: [e.g. 1.2.3] - -## Additional Information - -Provide any additional context or information about the bug. diff --git a/packages/overlays/overlay/.github/ISSUE_TEMPLATE/discussion.md b/packages/overlays/overlay/.github/ISSUE_TEMPLATE/discussion.md deleted file mode 100644 index 52aa083ce..000000000 --- a/packages/overlays/overlay/.github/ISSUE_TEMPLATE/discussion.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Discussion -about: Propose a discussion or seek clarification about a feature or topic. -title: "[DISCUSSION]" -labels: discussion -assignees: '' - ---- - -## Summary - -Briefly describe the topic you'd like to discuss. - -## Motivation - -Why do you believe this to be important? - -## Description - -Provide a detailed description or elaborate on your topic. - -## Additional References - -Provide any additional articles, links, or context that would help facilitate the discussion. diff --git a/packages/overlays/overlay/.github/pull_request_template.md b/packages/overlays/overlay/.github/pull_request_template.md deleted file mode 100644 index 035b61038..000000000 --- a/packages/overlays/overlay/.github/pull_request_template.md +++ /dev/null @@ -1,25 +0,0 @@ -## Description of Changes - -Provide a brief description of the changes you've made. - -## Linked Issues / Tickets - -Reference any related issues or tickets, e.g. "Closes #123". - -## Testing Procedure - -Describe the tests you've added or any testing steps you've taken. - -- [ ] I have added new unit tests -- [ ] All tests pass locally -- [ ] I have tested manually in my local environment - -## Checklist: - -- [ ] I have performed a self-review of my own code -- [ ] I have made corresponding changes to the documentation -- [ ] My changes generate no new warnings -- [ ] I have updated `CHANGELOG.md` with my changes -- [ ] I have run `npm run doc` and `npm run lint` one final time before requesting a review -- [ ] I have fixed all linter errors to ensure these changes are compliant with `ts-standard` -- [ ] I have run `npm version patch` so that my changes will trigger a new version to be released when they are merged \ No newline at end of file diff --git a/packages/overlays/overlay/.github/workflows/push.yaml b/packages/overlays/overlay/.github/workflows/push.yaml deleted file mode 100644 index c93f87e49..000000000 --- a/packages/overlays/overlay/.github/workflows/push.yaml +++ /dev/null @@ -1,73 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - master - pull_request: - - -jobs: - test: - name: Build & Test - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run lint:ci --if-present - - run: npm run test:coverage --if-present - - name: Upload coverage to Codecov - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - publish: - name: Publish - needs: test - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - - run: npm ci --ignore-scripts - - run: npm run build - - name: Check if version already published - id: check-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Version $PACKAGE_VERSION already published" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "Version $PACKAGE_VERSION not yet published" - echo "should_publish=true" >> $GITHUB_OUTPUT - fi - - name: Publish to npm - if: steps.check-version.outputs.should_publish == 'true' - run: npm publish - - name: Skip publishing - if: steps.check-version.outputs.should_publish == 'false' - run: echo "Skipping publish - version already exists on npm" diff --git a/packages/overlays/overlay/AGENTS.md b/packages/overlays/overlay/AGENTS.md index 84ac35e6e..dcea67c80 100644 --- a/packages/overlays/overlay/AGENTS.md +++ b/packages/overlays/overlay/AGENTS.md @@ -1,158 +1,10 @@ -# CLAUDE.md — @bsv/overlay +# ts-stack agent instructions -## Purpose +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -Core library defining the Overlay Services Engine for UTXO-based systems on BSV. Provides abstract `TopicManager` and `LookupService` interfaces, the `Engine` orchestrator, and Knex-based persistent storage. Enables dynamic tracking and management of applications built on top of the BSV blockchain. - -## Public API surface - -From `mod.ts`: - -- **`Engine`** — Class that orchestrates topic managers and lookup services - - Constructor: `new Engine(managers, lookupServices, storage, chainTracker, hostingURL, shipTrackers, slapTrackers, broadcaster, advertiser, syncConfiguration, logTime, logPrefix, throwOnBroadcastFailure, overlayBroadcastFacilitator, logger, suppressDefaultSyncAdvertisements)` - - Methods: `submit(taggedBEEF, callback)`, `lookup(question)`, `listTopicManagers()`, `listLookupServiceProviders()`, `getDocumentationForTopicManger()`, `getDocumentationForLookupServiceProvider()`, `handleNewMerkleProof()`, `provideForeignSyncResponse()`, `provideForeignGASPNode()` - -- **`TopicManager`** — Interface for admission logic - - `identifyAdmissibleOutputs(beef, previousCoins, offChainValues?, mode?)` → `Promise` - - `identifyNeededInputs?(beef, offChainValues?)` → `Promise<{txid, outputIndex}[]>` - - `getDocumentation()` → `Promise` - - `getMetaData()` → `Promise<{name, shortDescription, iconURL?, version?, informationURL?}>` - -- **`LookupService`** — Interface for querying admitted UTXOs - - Properties: `admissionMode` ('locking-script' | 'whole-tx'), `spendNotificationMode` ('none' | 'txid' | 'script' | 'whole-tx') - - Hooks: `outputAdmittedByTopic(payload)`, `outputSpent?(payload)`, `outputNoLongerRetainedInHistory?()`, `outputEvicted()` - - Query: `lookup(question)` → `Promise` - - Docs: `getDocumentation()`, `getMetaData()` - -- **`Storage`** — Persistent storage interface (from storage/Storage.ts) - - Methods for storing/retrieving transactions, UTXOs, outputs, admission records - -- **`KnexStorage`** — Knex-based SQL storage implementation - - Automatic migrations for SQLite/PostgreSQL/MySQL - - Exports: `KnexStorageMigrations` - -- **Types**: `TaggedBEEF`, `STEAK`, `LookupQuestion`, `LookupAnswer`, `AdmittanceInstructions`, `Output`, `LookupFormula`, `Advertisement`, `AdvertisementData`, `Advertiser` - -## Real usage patterns - -From README examples and test files: - -```typescript -// 1. Creating and configuring an Engine -const engine = new Engine( - { hello: new HelloTopicManager() }, - { hello: new HelloLookupService({ storageEngine: new HelloStorageEngine({ knex }) }) }, - new KnexStorage({ knex }), - new WhatsOnChain('main', { httpClient: new NodejsHttpClient(https) }), - 'https://example.com' -) - -// 2. Submitting transactions with topics -const topicsHeader = req.headers['x-topics'] -const topics = topicsHeader.trim().startsWith('[') - ? JSON.parse(topicsHeader) - : topicsHeader.split(',').map(topic => topic.trim()) -const taggedBEEF = { beef: Array.from(req.body), topics } -await engine.submit(taggedBEEF, (steak) => res.status(200).json(steak)) - -// 3. Performing lookups -const result = await engine.lookup({ - service: 'ls_hello', - query: { /* topic-specific query */ } -}) - -// 4. Implementing a TopicManager -class CustomTopicManager implements TopicManager { - async identifyAdmissibleOutputs(beef, previousCoins) { - const tx = new Transaction(beef) - return { outputsToAdmit: [0], coinsToRetain: [] } - } - async getDocumentation() { return 'Custom topic' } - async getMetaData() { return { name: 'custom', shortDescription: 'A custom topic' } } -} - -// 5. Implementing a LookupService -class CustomLookupService implements LookupService { - readonly admissionMode = 'locking-script' - readonly spendNotificationMode = 'none' - - async outputAdmittedByTopic(payload) { - if (payload.mode === 'locking-script') { - // Index the output - } - } - - async lookup(question) { - // Return LookupFormula with results - return { UTXOs: [...] } - } -} -``` - -## Key concepts - -- **TopicManager**: Validates which outputs are admissible to the overlay based on protocol rules -- **LookupService**: Indexes and queries admitted UTXOs; notified on admission/spend/eviction -- **Engine**: Orchestrates managers/services, handles transaction submission, UTXO history, SHIP/SLAP sync, GASP integration -- **Storage**: Abstracted persistence layer; Knex implementation handles SQL migrations -- **BEEF/STEAK**: Transaction encoding (BEEF = Bitcoins Efficiently Formatted; STEAK = payload format for engine responses) -- **AdmissionMode**: Whether lookup service receives locking-script details or whole transaction -- **SpendNotificationMode**: How lookup service is notified when a UTXO is spent (none, txid-only, full script, whole-tx) -- **GASP Integration**: Engine can sync with other overlay services using Graph Aware Sync Protocol -- **SHIP/SLAP**: Sync protocols for peer discovery (Service Host Interconnect Protocol, Service Lookup Availability Protocol) - -## Dependencies - -**Runtime:** -- `@bsv/gasp` — Graph Aware Sync Protocol for inter-service synchronization -- `@bsv/sdk` — BSV SDK for Transaction, ChainTracker, Broadcaster, MerklePath, LookupQuestion/Answer types -- `knex` — SQL query builder (for KnexStorage) - -**Dev:** -- jest, ts-jest, typescript, Oxlint - -## Common pitfalls / gotchas - -1. **Topic vs Service naming**: Topic managers are prefixed `tm_*`, lookup services `ls_*` by default in discovery -2. **BEEF encoding required**: All transactions must be submitted in BEEF format; raw hex will fail -3. **Previous coins array**: When resubmitting transactions spending prior outputs, must include indices of those inputs in `previousCoins` -4. **Knex migrations**: Custom storage implementations must handle schema creation; KnexStorage provides standard migrations -5. **GASP sync context**: SHIP/SLAP topics (tm_ship, tm_slap) have special handling for peer discovery configuration -6. **Metadata vs storage**: LookupService receives either locking-script OR whole-tx based on admissionMode; must implement accordingly -7. **Chain validation**: If chainTracker is 'scripts only', SPV proofs are not validated; useful for testing but unsafe for production - -## Spec conformance - -- Implements BSV Overlay protocol for UTXO tracking -- Supports SHIP (Service Host Interconnect Protocol) and SLAP (Service Lookup Availability Protocol) for peer discovery -- Integrates Graph Aware Sync Protocol (GASP) for historical synchronization with other overlay nodes - -## File map - -``` -src/ -├── Engine.ts — Main orchestrator class -├── TopicManager.ts — Topic manager interface -├── LookupService.ts — Lookup service interface (admissionMode, spendNotificationMode) -├── LookupFormula.ts — Query result type -├── Output.ts — Output record type -├── Advertisement.ts — Advertisement structure for peer discovery -├── Advertiser.ts — Advertiser interface -├── SyncConfiguration.ts — GASP sync config -├── GASP/ -│ ├── OverlayGASPRemote.ts — GASP remote implementation for Engine -│ └── OverlayGASPStorage.ts — GASP storage implementation for Engine -└── storage/ - ├── Storage.ts — Storage interface - └── knex/ - ├── KnexStorage.ts — Knex-based SQL storage - └── migrations/ — Knex migrations (2024-2025) -``` - -## Integration points - -- **@bsv/overlay-express**: Wraps Engine in Express HTTP server with /submit, /lookup, /listTopicManagers, /listLookupServiceProviders endpoints -- **@bsv/overlay-topics**: Provides pre-built topic managers and lookup services (hello, did, kvstore, btms, etc.) -- **@bsv/overlay-discovery-services**: Implements SHIP/SLAP for peer discovery; WalletAdvertiser for certificate-based advertisements -- **@bsv/gasp**: Graph Aware Sync Protocol; OverlayGASPStorage and OverlayGASPRemote adapt Engine to GASP interfaces -- **Custom implementations**: Any TopicManager/LookupService can be registered in Engine; storage can be replaced with custom backends +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/overlays/overlay/CONTRIBUTING.md b/packages/overlays/overlay/CONTRIBUTING.md deleted file mode 100644 index 5adf7fb8c..000000000 --- a/packages/overlays/overlay/CONTRIBUTING.md +++ /dev/null @@ -1,85 +0,0 @@ -# CONTRIBUTING to `@bsv/overlay` - -Thank you for considering contributing to the BSV Blockchain Overlay Services Engine Project! This document outlines the processes and practices we expect contributors to adhere to. - -## Table of Contents - -1. [General Guidelines](#general-guidelines) -2. [Code of Conduct](#code-of-conduct) -3. [Getting Started](#getting-started) -4. [Pull Request Process](#pull-request-process) -5. [Coding Conventions](#coding-conventions) -6. [Documentation and Testing](#documentation-and-testing) -7. [Contact & Support](#contact--support) - -## General Guidelines - -- **Issues First**: If you're planning to add a new feature or change existing behavior, please open an issue first. This allows us to avoid multiple people working on similar features and provides a place for discussion. - -- **Stay Updated**: Always pull the latest changes from the main branch before creating a new branch or starting on new code. - -- **Simplicity Over Complexity**: Your solution should be as simple as possible, given the requirements. - -## Code of Conduct - -### Posting Issues and Comments - -- **Be Respectful**: Everyone is here to help and grow. Avoid any language that might be considered rude or offensive. - -- **Be Clear and Concise**: Always be clear about what you're suggesting or reporting. If an issue is related to a particular piece of code or a specific error message, include that in your comment. - -- **Stay On Topic**: Keep the conversation relevant to the issue at hand. If you have a new idea or unrelated question, please open a new issue. - -### Coding and PRs - -- **Stay Professional**: Avoid including "fun" code, comments, or irrelevant file changes in your commits and pull requests. - -## Getting Started - -1. **Fork the Repository**: Click on the "Fork" button at the top-right corner of this repository. - -2. **Clone the Forked Repository**: `git clone https://github.com/YOUR_USERNAME/overlay-services.git` - -3. **Navigate to the Directory**: `cd overlay-services` - -4. **Install Dependencies**: Always run `npm i` after pulling to ensure tooling is up to date. - -## Pull Request Process - -1. **Create a Branch**: For every new feature or bugfix, create a new branch. - -2. **Commit Your Changes**: Make your changes and commit them. Commit messages should be clear and concise to explain what was done. - -3. **Run Tests**: Ensure all tests pass using Jest: `npm test`. - -4. **Documentation**: All code must be fully annotated with comments. Update the documentation by running `npm run doc` before creating a pull request. - -5. **Push to Your Fork**: `git push origin your-new-branch`. - -6. **Open a Pull Request**: Go to your fork on GitHub and click "New Pull Request". Fill out the PR template, explaining your changes. - -7. **Code Review**: At least two maintainers must review and approve the PR before it's merged. Address any feedback or changes requested. - -8. **Merge**: Once approved, the PR will be merged into the main branch. - -## Coding Conventions - -- **Code Style**: We use `Oxlint` for our TypeScript coding style. Run `npm run lint` to ensure your code adheres to this style. - -- **Minimal Runtime Dependencies**: Code should not depend on external systems where possible, with a strong preference for maintaining things internally. The one exception is the `@bsv/sdk` library dependency. - -- **Testing**: Always include tests for new code or changes. We aim for industry-standard levels of test coverage. - -- **Documentation**: All functions, classes, and modules should be documented. Use annotation comments to describe the purpose, parameters, and return values. - -## Documentation and Testing - -- **Documentation**: Update the documentation whenever you add or modify the code. Run `npm run doc` to generate the latest docs. - -- **Testing**: We use Jest for all tests. Write comprehensive tests, ensuring edge cases are covered. All PRs should maintain or improve the current test coverage. - -## Contact & Support - -If you have any questions or need assistance with your contributions, feel free to reach out. Remember, we're here to help each other grow and improve the `@bsv/overlay`. - -Thank you for being a part of this journey. Your contributions help shape the future of the BSV Blockchain! diff --git a/packages/overlays/topics/AGENTS.md b/packages/overlays/topics/AGENTS.md index 53f6a6440..dcea67c80 100644 --- a/packages/overlays/topics/AGENTS.md +++ b/packages/overlays/topics/AGENTS.md @@ -1,231 +1,10 @@ -# CLAUDE.md — @bsv/overlay-topics +# ts-stack agent instructions -## Purpose +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -Canonical collection of pre-built BSV overlay topic managers and lookup services. Ships ready-to-use implementations for common use cases (identity, tokens, supply chain, messaging, etc.), eliminating boilerplate for deploying overlay services. - -## Public API surface - -From `src/index.ts` — exports 20+ topic/lookup service pairs: - -**Core types:** -- `UTXOReference` — Shared reference type - -**any** — Catch-all topic accepting any PushDrop output -- `AnyTopicManager` — Admits any valid PushDrop -- `createAnyLookupService(db)` — Factory for MongoDB lookup -- Types: `AnyRecord`, `AnyQuery` - -**btms** — Basic Token Management System (moved from btms-backend) -- `BTMSTopicManager` — Token issuance/transfer validation -- `createBTMSLookupService(db)` — Token indexing/query -- Types: `BTMSQuery`, `BTMSRecord`, `BTMSLookupResult`; `btmsProtocol` constant - -**apps** — Application catalog -- `AppsTopicManager`, `createAppsLookupService(db)` -- Types: `AppCatalogQuery`, `PublishedAppMetadata`, `AppCatalogRecord` - -**basketmap** — Logical grouping of tokens -- `BasketMapTopicManager`, `createBasketMapLookupService(db)` -- Types: `BasketMapRegistration`, `BasketMapRecord`, `BasketMapQuery` - -**certmap** — Certificate mapping -- `CertMapTopicManager`, `createCertMapLookupService(db)` -- Types: `CertMapRegistration`, `CertMapRecord`, `CertMapQuery` - -**desktopintegrity** — Desktop integrity verification -- `DesktopIntegrityTopicManager`, `createDesktopIntegrityLookupService(db)` -- Type: `DesktopIntegrityRecord` - -**did** — Decentralized Identifiers -- `DIDTopicManager`, `createDIDLookupService(db)` -- Types: `DIDRecord`, `DIDQuery` - -**fractionalize** — Token fractionalization -- `FractionalizeTopicManager`, `createFractionalizeLookupService(db)` -- Types: `FractionalizeRecord`, `FractionalizeQuery` - -**hello** — Hello World demo topic -- `HelloWorldTopicManager`, `createHelloWorldLookupService(db)` -- Type: `HelloWorldRecord` - -**identity** — Identity attributes/claims -- `IdentityTopicManager`, `createIdentityLookupService(db)` -- Types: `IdentityAttributes`, `IdentityRecord`, `IdentityQuery` - -**kvstore** — Key-value store (protocol-agnostic) -- `KVStoreTopicManager`, `createKVStoreLookupService(db)` -- Types: `KVStoreQuery`, `KVStoreRecord`, `KVStoreLookupResult`; `kvProtocol` constant - -**message-box** — Inbox/messaging -- `MessageBoxTopicManager`, `createMessageBoxLookupService(db)` - -**monsterbattle** — Game state (demo app) -- `MonsterBattleTopicManager`, `createMonsterBattleLookupService(db)` -- Type: `MonsterBattleRecord` - -**protomap** — Protocol registry -- `ProtoMapTopicManager`, `deserializeWalletProtocol()`, `createProtoMapLookupService(db)` -- Types: `ProtoMapRegistration`, `ProtoMapRecord`, `ProtoMapQuery` - -**slackthreads** — Slack thread indexing -- `SlackThreadsTopicManager`, `createSlackThreadsLookupService(db)` -- Type: `SlackThreadRecord` - -**supplychain** — Supply chain tracking -- `SupplyChainTopicManager`, `createSupplyChainLookupService(db)` -- Type: `SupplyChainRecord` - -**uhrp** — Unified Hash Registry Protocol -- `UHRPTopicManager`, `createUHRPLookupService(db)` -- Type: `UHRPRecord` - -**ump** — Universal Messenger Protocol -- `UMPTopicManager`, `createUMPLookupService(db)` -- Type: `UMPRecord` - -**utility-tokens** — Fungible/NFT demo -- `TokenDemoTopicManager`, `createTokenDemoLookupService(db)` -- Types: `TokenDemoDetails`, `TokenDemoRecord`, `TokenDemoQuery` - -**walletconfig** — Wallet configuration -- `WalletConfigTopicManager`, `createWalletConfigLookupService(db)` -- Types: `WalletConfigRegistration`, `WalletConfigRecord`, `WalletConfigQuery` - -## Real usage patterns - -From test files: - -```typescript -// 1. Using HelloWorld topic -import { HelloWorldTopicManager, createHelloWorldLookupService } from '@bsv/overlay-topics' - -const manager = new HelloWorldTopicManager() -const result = await manager.identifyAdmissibleOutputs(beef, previousCoins) - -// Create lookup service (needs MongoDB Db instance) -const lookupService = await createHelloWorldLookupService(mongoDb) -const results = await lookupService.lookup({ - service: 'ls_hello', - query: { /* topic-specific */ } -}) - -// 2. Using DID topic -import { DIDTopicManager, createDIDLookupService } from '@bsv/overlay-topics' - -const didManager = new DIDTopicManager() -const admittance = await didManager.identifyAdmissibleOutputs(beef, []) -// Expects 2-field PushDrop: [serialNumber, signature] - -// 3. Using BTMS (token management) -import { BTMSTopicManager, createBTMSLookupService, BTMSQuery } from '@bsv/overlay-topics' - -const btmsManager = new BTMSTopicManager() -const btmsService = await createBTMSLookupService(mongoDb) -const tokens = await btmsService.lookup({ - service: 'ls_btms', - query: { assetId: 'txid.0' } as BTMSQuery -}) - -// 4. Using KVStore -import { KVStoreTopicManager, createKVStoreLookupService, kvProtocol } from '@bsv/overlay-topics' - -const kvManager = new KVStoreTopicManager() -const kvService = await createKVStoreLookupService(mongoDb) -// Query by key, owner, etc. - -// 5. Registering multiple managers in OverlayExpress -import OverlayExpress from '@bsv/overlay-express' -import { - HelloWorldTopicManager, createHelloWorldLookupService, - DIDTopicManager, createDIDLookupService, - KVStoreTopicManager, createKVStoreLookupService, - BTMSTopicManager, createBTMSLookupService -} from '@bsv/overlay-topics' - -const server = new OverlayExpress('mynode', privateKey, 'https://example.com') -server.configureTopicManager('tm_hello', new HelloWorldTopicManager()) -server.configureTopicManager('tm_did', new DIDTopicManager()) -server.configureTopicManager('tm_kvstore', new KVStoreTopicManager()) -server.configureTopicManager('tm_btms', new BTMSTopicManager()) - -await server.configureLookupServiceWithMongo('ls_hello', db => createHelloWorldLookupService(db)) -await server.configureLookupServiceWithMongo('ls_did', db => createDIDLookupService(db)) -await server.configureLookupServiceWithMongo('ls_kvstore', db => createKVStoreLookupService(db)) -await server.configureLookupServiceWithMongo('ls_btms', db => createBTMSLookupService(db)) - -await server.configureEngine() -await server.start() -``` - -## Key concepts - -- **Topic managers**: Each implements `TopicManager` interface, deciding which outputs are protocol-valid -- **Lookup services**: Each implements `LookupService` interface, indexing and querying admitted outputs in MongoDB -- **PushDrop encoding**: All topics use PushDrop format for data fields + signature/lock structure -- **Protocol-specific fields**: Each topic defines what fields it expects (e.g., DID expects [serialNumber, signature], BTMS expects [assetId, amount, metadata?]) -- **Query types**: Each topic defines its own `Query` and `Record` types for type-safe lookups -- **Lookup factories**: `create*LookupService(db)` functions that build and return configured services -- **MongoDB indexing**: All services auto-create indices on frequently-queried fields -- **Canonical exports**: All managers and services exported from index.ts for convenience - -## Dependencies - -**Runtime:** -- `@bsv/overlay` — TopicManager, LookupService interfaces -- `@bsv/sdk` — Transaction, LockingScript, PushDrop, PrivateKey, Signature, Utils -- `mongodb` — MongoDB driver (for lookup services) - -**Dev:** -- jest, ts-jest, typescript, Oxlint -- mongodb-memory-server — In-memory MongoDB for tests - -## Common pitfalls / gotchas - -1. **Lookup factories are async**: `createHelloWorldLookupService()` returns a Promise; must await -2. **MongoDB required**: All lookup services assume a MongoDB connection; no fallback to Knex -3. **PushDrop validation**: Each topic validates PushDrop structure; malformed scripts are rejected -4. **Field count varies**: DID requires exactly 2 fields; BTMS requires 2-4 fields; violations are rejected -5. **Signature validation**: Most topics verify signatures; invalid signatures cause rejection -6. **BTMS asset ID semantics**: "ISSUE" in assetId field = new token; otherwise must match previous issuance -7. **Metadata fields**: Some topics (BTMS, Certmap) have optional metadata; must handle both presence and absence -8. **Topic naming conventions**: By convention, managers prefixed `tm_*`, services `ls_*` (e.g., `tm_btms`, `ls_btms`) - -## Spec conformance - -- **DID**: DIDs as per W3C patterns (serialNumber as DID identifier) -- **BTMS**: Basic Token Management System protocol (issuance, transfer, burn) -- **KVStore**: Key-value protocol-agnostic; supports arbitrary key-value pairs -- **ProtoMap**: Registry of wallet protocols; supports deserialization via `deserializeWalletProtocol()` -- **UHRP**: Unified Hash Registry Protocol -- **UMP**: Universal Messenger Protocol -- **All**: Use PushDrop for encoding (see @bsv/sdk) - -## File map - -``` -src/ -├── index.ts — All exports (20+ managers and services) -├── any/ -│ ├── types.ts -│ ├── AnyTopicManager.ts -│ └── AnyLookupService.ts -├── btms/ -│ ├── types.ts -│ ├── BTMSTopicManager.ts -│ └── BTMSLookupService.ts -├── apps/, basketmap/, certmap/, desktopintegrity/, did/, fractionalize/, hello/ -│ identity/, kvstore/, message-box/, monsterbattle/, protomap/, slackthreads/ -│ supplychain/, uhrp/, ump/, utility-tokens/, walletconfig/ -│ └── [Similar structure: types.ts, TopicManager.ts, LookupService.ts] -└── __tests__/ - └── [Test files for each topic: hello.test.ts, did.test.ts, btms.test.ts, etc.] -``` - -## Integration points - -- **@bsv/overlay**: Implements TopicManager/LookupService interfaces -- **@bsv/overlay-express**: Register managers/services via `configureTopicManager()` and `configureLookupServiceWithMongo()` -- **@bsv/overlay-discovery-services**: SHIP/SLAP can advertise any of these topics for peer discovery -- **@bsv/gasp**: Any topic can participate in Graph Aware Sync if Engine is configured for GASP -- **Custom topics**: Can be implemented separately and registered alongside these canonical ones +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/sdk/AGENTS.md b/packages/sdk/AGENTS.md index 9b682dded..da1c81f82 100644 --- a/packages/sdk/AGENTS.md +++ b/packages/sdk/AGENTS.md @@ -1,283 +1,10 @@ -# CLAUDE.md — @bsv/sdk v2.0.14 +# ts-stack agent instructions -## Purpose -The BSV TypeScript SDK is the foundational cryptographic and transaction library for the BSV blockchain. It provides low-level primitives (keys, signatures, hashing), script construction and execution, transaction creation and signing, and integration interfaces for wallets and overlay networks. Every other ts-stack package builds on top of this. +This project follows the repository-wide [agent instructions](../../AGENTS.md) +and [contribution policy](../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -## Public API Surface - -### Primitives (from `src/primitives/`) -- **`PrivateKey`** — Elliptic curve private key for ECDSA signatures; methods: `toPublicKey()`, `toAddress()`, `toWif()`, `fromWif()`, `sign()`, `fromRandom()` -- **`PublicKey`** — Public key point; methods: `toHash()` (P2PKH hash), `toAddress()`, `verify(message, signature)` -- **`Hash`** — Hash utilities: `hash256(data)`, `hash160(data)` for SHA-256 and RIPEMD-160 -- **`BigNumber`** — Arbitrary precision arithmetic for satoshi amounts -- **`TransactionSignature`** — ECDSA signature with sighash type -- **`Curve`** — secp256k1 elliptic curve operations -- **`Entropy`** — Random number generation for key derivation - -### Script (from `src/script/`) -- **`Script`** — Base class for locking/unlocking scripts; methods: `toHex()`, `toArray()`, `fromHex()`, `chunks()` -- **`LockingScript`** — Output lock constraints; immutable once attached to output -- **`UnlockingScript`** — Signature data to satisfy locking script -- **`ScriptChunk`** — Individual operation code and data; methods: `toHex()`, `toBN()` -- **`OP`** — Bitcoin script operation codes (enum-like): `OP.OP_DUP`, `OP.OP_HASH160`, `OP.OP_EQUAL`, etc. - -### Script Templates (from `src/script/templates/`) -- **`P2PKH`** — Pay-to-Public-Key-Hash; methods: `lock(pubKeyHash)`, `unlock(privateKey)` for standard payments -- **`P2PK`** — Pay-to-Public-Key (legacy) -- **`P2SH`** — Pay-to-Script-Hash -- **`PushDrop`** — Proof-carrying data envelope for overlay protocols; methods: `createFromFields(...)` to encode multi-field data - -### Transaction (from `src/transaction/`) -- **`Transaction`** — Complete transaction builder; constructor signature: `new Transaction(version?, inputs?, outputs?)`; methods: - - `addInput(sourceTransaction, sourceOutputIndex, unlockingScriptTemplate)` - - `addOutput(lockingScript, satoshis, outputDescription?)` - - `sign()` — Requires wallet implementation via `SignableTransaction` - - `fee()` — Estimate/calculate fees - - `broadcast()` — Send to network - - `toHex()`, `fromHex(hex)`, `id` (txid getter) -- **`Input`** — Single input reference with script template -- **`Output`** — Single output with locking script and satoshi amount -- **`MerklePath`** — Merkle inclusion proof for SPV verification -- **`Beef`** — BRC-62 "BEEF" envelope for atomic transaction batches; constants: `BEEF_V1`, methods: `fromHex()`, `toHex()` -- **`BroadcastResponse`** — Standardized response from broadcasters (ARC, WhatsOnChain, etc.) - -### Fee Models (from `src/transaction/fee-models/`) -- **`SatoshisPerKilobyte`** — Linear fee rate; constructor: `new SatoshisPerKilobyte(satoshisPerKb)` -- **`LivePolicy`** — Network-aware fee estimation from chain trackers - -### Broadcasters (from `src/transaction/broadcasters/`) -- **`ARC`** — Arc Network transaction submission; methods: `broadcast(tx)` → `Promise` -- **`WhatsOnChainBroadcaster`** — WhatsOnChain API integration -- **`Teranode`** — Teranode broadcaster -- **`Broadcaster`** — Base interface for all broadcasters - -### Chain Trackers (from `src/transaction/chaintrackers/`) -- **`DefaultChainTracker`** — In-memory blockchain state tracking -- **`WhatsOnChainChainTracker`** — Remote chain state via WhatsOnChain API -- **`BlockHeadersService`** — Lightweight header service for SPV - -### HTTP (from `src/transaction/http/`) -- **`DefaultHttpClient`** — Node.js HTTP client for remote services -- **`BinaryFetchClient`** — Browser fetch-based HTTP client - -### Messages (from `src/messages/`) -- **`Message`** — Signed message container -- Message signing/verification for BRC-18 and other standards - -### Wallet (from `src/wallet/`) -- **`WalletInterface`** — BRC-100 standardized wallet interface (peer dependency) -- **`CreateActionArgs`**, **`CreateActionResult`** — Transaction creation request/response -- **`SignActionArgs`**, **`SignActionResult`** — Signing request/response -- **`ListOutputsArgs`**, **`ListActionsArgs`** — UTXO and action history queries -- **`ProtoWallet`** — Minimal in-memory wallet for testing and local use -- **`WalletClient()`** — Factory for connecting to standard BRC-100 wallets (desktop/browser) - -### Auth (from `src/auth/`) -- **`Certificate`** — X.509-like certificate for peer authentication -- **`IdentityKey`** — Public identity verification -- **`AuthModule`** — Pluggable authentication mechanisms - -### Substrates (from `src/wallet/substrates/`) -- **`Substrate`** — Pluggable signature provider interface -- Implementation adapters for hardware wallets, custody services, etc. - -### Storage (from `src/storage/`) -- **`Storage`** — KV store interface -- **`LocalStorageAdapter`** — Browser localStorage -- **`InMemoryStorage`** — Ephemeral storage for testing - -### KVStore (from `src/kvstore/`) -- **`KVStore`** — Distributed key-value store interface for immutable data - -### Remittance (from `src/remittance/`) -- **`Remittance`** — Payment protocol for overlay services - -### Identity (from `src/identity/`) -- **`Identity`** — Identity verification and discovery -- **`IdentityResolver`** — Lookup services - -### Registry (from `src/registry/`) -- **`Registry`** — Protocol/certificate registration -- Overlay service discovery - -### Compat (from `src/compat/`) -- **`fromUtxo(utxo)`** — Adapter to convert legacy UTXO format to SDK Input -- Backward compatibility helpers - -### TOTP (from `src/totp/`) -- **`generateTOTP(secret)`** — Time-based one-time password for 2FA -- **`verifyTOTP(token, secret)`** — Validate TOTP token - -### Overlay Tools (from `src/overlay-tools/`) -- **`TopicBroadcaster`** — Broadcast messages to topic-based overlay networks -- **`TopicListener`** — Subscribe to overlay topics -- Overlay service integration - -## Real Usage Patterns - -### 1. Create and sign a basic P2PKH transaction -```typescript -import { PrivateKey, P2PKH, Transaction } from '@bsv/sdk' - -const privKey = PrivateKey.fromWif('L5EY1SbTvvPNSdCYQe1EJHfXCBBT4PmnF6CDbzCm9iifZptUvDGB') -const sourceTransaction = Transaction.fromHex('0200000001...') // Previous tx hex - -const tx = new Transaction(1, [ - { - sourceTransaction, - sourceOutputIndex: 0, - unlockingScriptTemplate: new P2PKH().unlock(privKey) - } -], [ - { - lockingScript: new P2PKH().lock(privKey.toAddress()), - satoshis: 5000, - change: true - } -]) - -await tx.fee() -await tx.sign() -const broadcast = await tx.broadcast() -``` - -### 2. Work with wallet interface for multi-signature or hardware signing -```typescript -import { WalletClient, Transaction, CreateActionArgs } from '@bsv/sdk' - -const wallet = WalletClient() // Connect to browser/desktop wallet -const tx = new Transaction() -tx.addOutput({ satoshis: 1000, ... }) - -// Use wallet for signing instead of local key -const actionResult = await wallet.createAction({ - description: 'Payment transaction', - outputs: tx.outputs.map(o => ({ ...o, outputDescription: 'payment' })) -}) - -const signResult = await wallet.signAction({ - actionReference: actionResult.signableTransaction.reference -}) -``` - -### 3. Verify SPV with merkle proof -```typescript -import { MerklePath, Transaction } from '@bsv/sdk' - -const tx = Transaction.fromHex('...') -const merklePath = MerklePath.fromHex('...') - -if (merklePath.verify(tx.id, blockHeight, blockHeaderHash)) { - console.log('Transaction is SPV-proven') -} -``` - -### 4. Encode token/data on-chain with PushDrop -```typescript -import { PushDrop, Script } from '@bsv/sdk' - -const tokenFields = ['myAssetId', '100', JSON.stringify({ name: 'MyToken' })] -const tokenScript = PushDrop.createFromFields(tokenFields) -const output = { lockingScript: tokenScript, satoshis: 1 } -``` - -### 5. Handle chain state and fee estimation -```typescript -import { DefaultChainTracker, SatoshisPerKilobyte, Transaction } from '@bsv/sdk' - -const tracker = new DefaultChainTracker() -const feeModel = new SatoshisPerKilobyte(1) // 1 sat/byte - -const tx = new Transaction() -// ... add inputs/outputs ... -const estimatedFee = await tx.fee(feeModel, tracker) -``` - -## Key Concepts - -- **Private Key** — 256-bit value from which all wallet operations derive. Never exposed in network traffic. -- **Public Key** — Elliptic curve point derived from private key; used for address generation and signature verification. -- **Script** — Combination of operation codes and data that define spending conditions. Locking scripts constrain outputs; unlocking scripts unlock them. -- **Transaction** — Atomic unit of blockchain state change. Inputs reference previous outputs (UTXOs); outputs create new UTXOs. -- **UTXO** — Unspent Transaction Output; identified by (txid, outputIndex). Spending requires a valid unlocking script. -- **Signature** — ECDSA signature with sighash byte indicating which transaction fields are committed to. -- **Merkle Proof** — Proof of inclusion in a block; enables SPV without downloading full blocks. -- **BEEF** — BRC-62 envelope; atomic bundle of transactions with merkle proofs for offline verification. -- **Wallet Interface (BRC-100)** — Standardized interface for wallet RPC between apps and wallet services. Abstracts away key management. -- **Overlay** — Second-layer protocol using on-chain anchors (PushDrop) to build services without blockchain modifications. - -## Dependencies - -### Runtime -- **None** (SDK is intentionally standalone for browser and Node.js) -- Uses built-in Node.js crypto in server context, WebCrypto in browser - -### Peer Deps (optional) -- `ws` — For WebSocket overlay connections (optional) -- `qrcode` — For QR code generation in wallet pairing (optional) - -### Other ts-stack packages -- None (SDK is at the base; other packages depend on this) - -## Common Pitfalls / Gotchas - -1. **Sighash commit mismatch** — Unlocking script hash commits only to parts of the transaction. If you modify tx after signing, signature becomes invalid. Always sign last. - -2. **Fee estimation timing** — `tx.fee()` may vary if mempool conditions change. Estimate early and buffer for volatility, or use live fee trackers. - -3. **UTXO reuse across parallel transactions** — If two transactions reference the same UTXO, only one will confirm. Wallet implementations must track pending outputs. - -4. **Script evaluation order** — Unlocking script is evaluated first, then locking script. Stack must be left with true atop for success. - -5. **Broadcast endpoint differences** — ARC, WhatsOnChain, Teranode have different response formats and rate limits. Implement retry logic and fallback chains. - -6. **Key derivation paths** — Different protocols (BRC-42, BRC-43) use different paths. Verify derivation matches wallet's expectations to avoid fund loss. - -7. **Browser vs Node.js API** — Some transaction methods (broadcast, network calls) behave differently in browser due to CORS. Test both contexts. - -8. **Merkle proof validity** — `MerklePath.verify()` requires exact block height and header hash. Off-by-one errors or header mismatch will fail verification. - -9. **Input spending order** — Scripts are evaluated in input order. If an early input fails, later inputs aren't executed. Order matters for deterministic behavior. - -10. **Satoshi precision** — Use `BigNumber` for satoshi arithmetic to avoid floating-point errors. Direct number arithmetic can lose precision above 2^53. - -## Spec Conformance - -- **BRC-18** — Signed messages -- **BRC-29** — Bitcoin Envelope (UTXO-addressed messages) -- **BRC-42, BRC-43** — Key derivation protocols -- **BRC-62** — BEEF (transaction envelope format) -- **BRC-100** — Wallet interface standard (exposed but not implemented) -- **SPV** — Full merkle proof verification support -- **Bitcoin Script** — Full consensus-rule-compliant interpreter - -## File Map - -- **`src/primitives/`** — Cryptographic primitives (keys, hashes, signatures) -- **`src/script/`** — Script classes and OP code definitions -- **`src/script/templates/`** — Standard script templates (P2PKH, P2SH, PushDrop, etc.) -- **`src/transaction/`** — Transaction building, signing, broadcasting -- **`src/transaction/fee-models/`** — Fee estimation strategies -- **`src/transaction/broadcasters/`** — Network integration (ARC, WhatsOnChain, Teranode) -- **`src/transaction/chaintrackers/`** — Blockchain state tracking -- **`src/transaction/http/`** — HTTP client abstractions -- **`src/messages/`** — Message signing and verification -- **`src/wallet/`** — BRC-100 wallet interface definitions -- **`src/auth/`** — Authentication and certificates -- **`src/storage/`** — Storage adapters -- **`src/kvstore/`** — KV store interface -- **`src/remittance/`** — Payment protocols -- **`src/identity/`** — Identity verification -- **`src/registry/`** — Protocol registry -- **`src/compat/`** — Legacy format adapters -- **`src/totp/`** — Two-factor authentication -- **`src/overlay-tools/`** — Overlay network integration -- **`mod.ts`** — Main entry point exporting all public APIs - -## Integration Points - -- **@bsv/wallet-toolbox** — Builds persistent wallet storage and signing on top of SDK; uses SDK's `WalletInterface` and transaction APIs -- **@bsv/btms** — Token issuance/transfer via PushDrop script encoding; uses SDK's Transaction and Script APIs -- **@bsv/btms-permission-module** — BTMS wallet integration; uses SDK's wallet interface -- **@bsv/wallet-relay** — Mobile wallet pairing protocol; uses SDK's cryptography and wallet interface -- **Direct app usage** — Any BSV app can import SDK directly for standalone transaction creation without wallet integration +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/sdk/BENCHMARK.md b/packages/sdk/BENCHMARK.md index 70ff1b968..6d7023b01 100644 --- a/packages/sdk/BENCHMARK.md +++ b/packages/sdk/BENCHMARK.md @@ -23,7 +23,7 @@ their own timing summaries. These programs are diagnostics, not a blocking performance claim. Hardware, power mode, thermal state, Node/V8 patch level, and background load materially -affect results. The final QA phase tracked by GitHub issue #324 owns stable +affect results. The final QA phase tracked by GitHub issue #400 owns stable hosted hardware, machine-readable baselines, variance policy, historical artifacts, and any future regression threshold. Until that work is complete, do not copy a local result into documentation as a universal SDK baseline. diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md index 71d65271c..e495bc0ce 100644 --- a/packages/sdk/CHANGELOG.md +++ b/packages/sdk/CHANGELOG.md @@ -230,6 +230,8 @@ All notable changes to this project will be documented in this file. The format ### Changed +- Point SDK contributors and AI agents to the canonical stack-level + contribution and quality policy without changing package behavior. - Harden UTF-8, binary JSON, secret-redaction, cryptographic compatibility, and optional-state handling while preserving the existing SDK 2.x wire and call contracts. diff --git a/packages/sdk/CONTRIBUTING.md b/packages/sdk/CONTRIBUTING.md deleted file mode 100644 index cd4f0083c..000000000 --- a/packages/sdk/CONTRIBUTING.md +++ /dev/null @@ -1,85 +0,0 @@ -# CONTRIBUTING to `@bsv/sdk` - -Thank you for considering contributing to the BSV Blockchain Libraries Project! This document outlines the processes and practices we expect contributors to adhere to. - -## Table of Contents - -1. [General Guidelines](#general-guidelines) -2. [Code of Conduct](#code-of-conduct) -3. [Getting Started](#getting-started) -4. [Pull Request Process](#pull-request-process) -5. [Coding Conventions](#coding-conventions) -6. [Documentation and Testing](#documentation-and-testing) -7. [Contact & Support](#contact--support) - -## General Guidelines - -- **Issues First**: If you're planning to add a new feature or change existing behavior, please open an issue first. This allows us to avoid multiple people working on similar features and provides a place for discussion. - -- **Stay Updated**: Always pull the latest changes from the main branch before creating a new branch or starting on new code. - -- **Simplicity Over Complexity**: Your solution should be as simple as possible, given the requirements. - -## Code of Conduct - -### Posting Issues and Comments - -- **Be Respectful**: Everyone is here to help and grow. Avoid any language that might be considered rude or offensive. - -- **Be Clear and Concise**: Always be clear about what you're suggesting or reporting. If an issue is related to a particular piece of code or a specific error message, include that in your comment. - -- **Stay On Topic**: Keep the conversation relevant to the issue at hand. If you have a new idea or unrelated question, please open a new issue. - -### Coding and PRs - -- **Stay Professional**: Avoid including "fun" code, comments, or irrelevant file changes in your commits and pull requests. - -## Getting Started - -1. **Fork the Repository**: Click on the "Fork" button at the top-right corner of this repository. - -2. **Clone the Forked Repository**: `git clone https://github.com/YOUR_USERNAME/ts-sdk.git` - -3. **Navigate to the Directory**: `cd bsv-sdk` - -4. **Install Dependencies**: Since we maintain all code within this library, there shouldn't be any runtime dependencies. Nevertheless, always run `npm install` after pulling to ensure tooling is up to date. - -## Pull Request Process - -1. **Create a Branch**: For every new feature or bugfix, create a new branch. - -2. **Commit Your Changes**: Make your changes and commit them. Commit messages should be clear and concise to explain what was done. - -3. **Run Tests**: Ensure all tests pass using Jest: `npm test`. - -4. **Documentation**: All code must be fully annotated with comments. Update the documentation by running `npm run doc` before creating a pull request. - -5. **Push to Your Fork**: `git push origin your-new-branch`. - -6. **Open a Pull Request**: Go to your fork on GitHub and click "New Pull Request". Fill out the PR template, explaining your changes. - -7. **Code Review**: At least two maintainers must review and approve the PR before it's merged. Address any feedback or changes requested. - -8. **Merge**: Once approved, the PR will be merged into the main branch. - -## Coding Conventions - -- **Code Style**: We use `Oxlint` for our TypeScript coding style. Run `npm run lint` to ensure your code adheres to this style. - -- **No Runtime Dependencies**: All code should be maintained within this library. Do not introduce external dependencies. - -- **Testing**: Always include tests for new code or changes. We aim for industry-standard levels of test coverage. - -- **Documentation**: All functions, classes, and modules should be documented. Use annotation comments to describe the purpose, parameters, and return values. - -## Documentation and Testing - -- **Documentation**: Update the documentation whenever you add or modify the code. Run `npm run doc` to generate the latest docs. - -- **Testing**: We use Jest for all tests. Write comprehensive tests, ensuring edge cases are covered. All PRs should maintain or improve the current test coverage. - -## Contact & Support - -If you have any questions or need assistance with your contributions, feel free to reach out. Remember, we're here to help each other grow and improve the `@bsv/sdk`. - -Thank you for being a part of this journey. Your contributions help shape the future of the BSV Blockchain Libraries Project! diff --git a/packages/sdk/README.md b/packages/sdk/README.md index a3407ef39..55fe03d33 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -152,7 +152,8 @@ We're always looking for contributors to help us improve the SDK. Whether it's b [Development and Distribution](#development-and-distribution). 5. **Commit**: Commit your changes and push to your fork. 6. **Pull Request**: Open a pull request from your fork to this repository. - For more details, check the [contribution guidelines](./CONTRIBUTING.md). + For more details, check the + [repository contribution guidelines](https://github.com/bsv-blockchain/ts-stack/blob/main/CONTRIBUTING.md). For information on past releases, check out the [changelog](./CHANGELOG.md). For future plans, check the [roadmap](./ROADMAP.md)! diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 79641e8aa..e1d1f99da 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@bsv/sdk", - "version": "2.2.14", + "version": "2.2.15", "sideEffects": false, "engines": { "node": ">=22" diff --git a/packages/verifast/AGENTS.md b/packages/verifast/AGENTS.md new file mode 100644 index 000000000..da1c81f82 --- /dev/null +++ b/packages/verifast/AGENTS.md @@ -0,0 +1,10 @@ +# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](../../AGENTS.md) +and [contribution policy](../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/wallet/btms-permission-module/AGENTS.md b/packages/wallet/btms-permission-module/AGENTS.md index 98ca552c8..dcea67c80 100644 --- a/packages/wallet/btms-permission-module/AGENTS.md +++ b/packages/wallet/btms-permission-module/AGENTS.md @@ -1,223 +1,10 @@ -# CLAUDE.md — @bsv/btms-permission-module v1.0.1 +# ts-stack agent instructions -## Purpose -This is the core permission module for BTMS token operations — framework-agnostic with no UI dependencies. It implements the BRC-98/99 permission hooks interface to intercept BTMS token spend and burn operations, prompt users via a callback, and enforce authorization decisions. Works with any UI framework (React, Vue, Angular, vanilla JS, or no UI at all). For React/MUI components, see the separate `@bsv/btms-permission-module-ui` package. +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -## Public API Surface - -### Main Class -- **`BasicTokenModule`** — BRC-98/99 permission module implementation; constructor: `new BasicTokenModule(promptHandler: PermissionPromptHandler, btms?: BTMS)`; methods: - - `canPerform(args: ModulePermissionArgs)` → `Promise` — Checks if app can perform token operation (spend/burn) - - `promptUser(app: string, message: string)` → `Promise` — Calls the provided prompt handler - - Implements `WalletPermissionModule` interface for wallet-toolbox integration - -### Factory Function -- **`createBtmsModule(args: PermissionModuleFactoryArgs)`** → `BasicTokenModule` — Convenience factory that creates both BTMS instance and module in one step; args: - - `wallet: WalletInterface` — Wallet instance - - `promptHandler?: PermissionPromptHandler` — Optional callback (defaults to deny-all) - -### Type Definitions -- **`PermissionPromptHandler`** — Callback signature: `(app: string, message: string) => Promise` - - `app` — Originating app identifier (e.g., "https://myapp.com") - - `message` — JSON string containing token spend details (see below for schema) - - Returns: `true` if user approved, `false` if denied -- **`PermissionModuleFactoryArgs`** — Factory options: `{ wallet, promptHandler? }` - -### Data Structures Passed to Handler -The `message` parameter passed to `promptHandler` is a JSON string containing: -```typescript -{ - tokenName?: string, // Token asset name if available - assetId: string, // Canonical asset ID (txid.vout) - sendAmount?: number, // Tokens being spent - burnAmount?: number, // Tokens being burned - recipientKey?: string, // (For send) recipient identity key - operation: 'spend' | 'burn' // Which operation -} -``` - -Your prompt handler must parse this JSON and present it to the user. - -## Real Usage Patterns - -### 1. Simple prompt with confirm dialog (vanilla JS) -```typescript -import { BasicTokenModule } from '@bsv/btms-permission-module' - -const requestTokenAccess = async (app: string, message: string): Promise => { - const details = JSON.parse(message) - - const approved = confirm( - `${app} wants to ${details.operation} ${details.sendAmount || details.burnAmount} of "${details.tokenName}"` - ) - - return approved -} - -const basicTokenModule = new BasicTokenModule(requestTokenAccess) -``` - -### 2. Custom modal dialog with React hook (without using btms-permission-module-ui) -```typescript -import { useState, useCallback } from 'react' -import { BasicTokenModule } from '@bsv/btms-permission-module' - -function MyTokenPermissionComponent() { - const [pendingRequest, setPendingRequest] = useState<{ app: string; message: string } | null>(null) - const [approved, setApproved] = useState(null) - - const handlePrompt = useCallback(async (app: string, message: string): Promise => { - return new Promise((resolve) => { - setPendingRequest({ app, message }) - - // Wait for user action (approve/deny buttons on dialog) - const onApprove = () => { - setPendingRequest(null) - resolve(true) - } - const onDeny = () => { - setPendingRequest(null) - resolve(false) - } - - // Store callbacks for dialog buttons to call - window._tokenPermissionCallbacks = { onApprove, onDeny } - }) - }, []) - - const module = new BasicTokenModule(handlePrompt) - - return ( - <> - {pendingRequest && ( - -

App "{pendingRequest.app}" requests token access

-

{pendingRequest.message}

- - -
- )} - - ) -} -``` - -### 3. Deny-all for programmatic use (no UI needed) -```typescript -import { createBtmsModule } from '@bsv/btms-permission-module' - -// Create module with no prompt handler — all requests denied -const module = createBtmsModule({ - wallet, - // No promptHandler → defaults to deny all -}) - -// Useful for server-side or automated workflows where token ops should be restricted -``` - -### 4. Register with wallet permissions manager -```typescript -import { WalletPermissionsManager } from '@bsv/wallet-toolbox' -import { BasicTokenModule } from '@bsv/btms-permission-module' - -const tokenModule = new BasicTokenModule(myPromptHandler) - -const permissionsManager = new WalletPermissionsManager(wallet, appOrigin, { - permissionModules: { - btms: tokenModule // Register under 'btms' key - } -}) - -// Now when app tries to spend BTMS tokens, permission system will: -// 1. Check if app has permission for 'btms' protocol -// 2. Call module's promptUser() if permission not cached -// 3. Ask user via your prompt handler -// 4. Cache decision for session -``` - -### 5. Parse token details in handler -```typescript -const myDetailedHandler = async (app: string, message: string): Promise => { - const details = JSON.parse(message) - - const action = details.operation === 'burn' ? 'permanently destroy' : 'transfer' - const recipient = details.recipientKey ? `to ${details.recipientKey.slice(0, 6)}...` : '' - const amount = details.sendAmount ?? details.burnAmount - - const confirmed = await showMyCustomDialog({ - title: 'Token Permission Required', - app, - action: `${action} ${amount} of ${details.tokenName || details.assetId} ${recipient}`, - severity: details.operation === 'burn' ? 'warning' : 'info' - }) - - return confirmed -} - -const module = new BasicTokenModule(myDetailedHandler) -``` - -## Key Concepts - -- **BRC-98/99 Hooks** — Standard permission module interface. Wallets invoke hooks when apps request special operations. Module returns true/false (allowed/denied). -- **Permission Caching** — Wallet-toolbox's `WalletPermissionsManager` caches yes/no decisions per (app, protocol) for the session. One prompt per app per transaction flow. -- **Framework Agnostic** — Module is pure TypeScript with no UI dependencies. You control how prompts appear (modal, alert, web component, etc.). -- **JSON Message Format** — Token details are serialized as JSON for transport. Your handler must parse to extract details for UI. -- **Async Prompt** — Handler returns a Promise so you can show UI, wait for user input, then resolve with decision. - -## Dependencies - -### Runtime (Peer Deps from package.json) -- **`@bsv/sdk`** ^2.0.14 — For types (WalletInterface) -- **`@bsv/btms`** ^1.0.1 — BTMS class for token validation (optional; used in factory) -- **`@bsv/wallet-toolbox-client`** ^2.1.18 — For wallet types - -### Dev -- **`@types/node`** ^25.6.0 — Node.js types -- **`typescript`** ^5.2.2 — Compiler - -### Other ts-stack packages -- **`@bsv/sdk`** — Type imports only (WalletInterface) -- **`@bsv/btms`** — Used in factory but optional -- **`@bsv/wallet-toolbox`** — Register module with WalletPermissionsManager - -## Common Pitfalls / Gotchas - -1. **JSON parse errors** — If the message format is wrong or contains invalid JSON, `JSON.parse()` will throw. Always wrap in try/catch in production handlers. - -2. **Async handler blocking** — If your prompt handler takes too long (e.g., waiting for user interaction), the wallet operation times out. Keep handlers responsive; use timeouts for user input. - -3. **Promise never resolving** — If your handler never calls `resolve()` (e.g., dialog closes without user clicking approve/deny), the wallet operation hangs. Always ensure resolve/reject is called. - -4. **Caching across sessions** — Wallet's `WalletPermissionsManager` caches decisions within a session. If app is reloaded, caches clear. Don't assume persistent caching. - -5. **Handler called multiple times** — For a single token send, handler may be called once per output. Apps spending 5 tokens in separate outputs = 5 handler calls. Batch prompts if possible in your UI. - -6. **Missing operation field** — Older versions or malformed messages may not include operation field. Default to 'spend' if ambiguous. - -7. **No token name available** — If asset hasn't been discovered yet, tokenName may be undefined. Use assetId as fallback for display. - -8. **Handler rejection is final** — If handler returns false, the operation fails with "Permission denied". There's no automatic retry. Users must approve first, then retry the operation. - -9. **BTMS instance not optional** — `createBtmsModule()` creates a new BTMS instance. If you already have one, construct `BasicTokenModule` directly instead. - -10. **No audit trail** — Module doesn't log approvals/denials. If you need audit trail, add logging to your handler. - -## Spec Conformance - -- **BRC-98/99** — Permission module interface (implements `canPerform()` and `promptUser()`) -- **BRC-100** — Uses standard wallet interface for token validation -- **BTMS** — Understands BTMS token operations (spend, burn) - -## File Map - -- **`src/index.ts`** — Public exports and factory function -- **`src/BasicTokenModule.ts`** — Main module class implementing BRC-98/99 -- **`src/types.ts`** — TypeScript interfaces (PermissionPromptHandler, etc.) - -## Integration Points - -- **@bsv/wallet-toolbox** — Module is registered with `WalletPermissionsManager.permissionModules` -- **@bsv/btms** — Validates token amounts and operations -- **@bsv/sdk** — Uses WalletInterface types -- **Your UI framework** — You provide the prompt handler that shows UI (React, Vue, etc.) +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/wallet/btms/AGENTS.md b/packages/wallet/btms/AGENTS.md index a63a87ce2..dcea67c80 100644 --- a/packages/wallet/btms/AGENTS.md +++ b/packages/wallet/btms/AGENTS.md @@ -1,236 +1,10 @@ -# CLAUDE.md — @bsv/btms v1.0.1 +# ts-stack agent instructions -## Purpose -BTMS (Basic Token Management System) is a modular library for issuing, sending, receiving, and burning UTXO-based tokens on the BSV blockchain. It provides high-level APIs for token operations (issue, send, accept, burn) while handling low-level details like PushDrop encoding, UTXO selection, transaction construction, and overlay service integration. Tokens are first-class on-chain objects identified by canonical asset IDs derived from transaction output references. +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -## Public API Surface - -### Main Class -- **`BTMS`** — Core token management class; constructor: `new BTMS(config?: BTMSConfig)`; methods: - - `issue(amount: number, metadata?: BTMSAssetMetadata)` → `Promise` — Create new token with amount and optional name/description/iconURL - - `send(assetId: string, recipientKey: string, amount: number)` → `Promise` — Transfer tokens to recipient's identity key - - `accept(payment: IncomingToken)` → `Promise` — Accept incoming token payment - - `burn(assetId: string, amount?: number)` → `Promise` — Permanently destroy tokens - - `getBalance(assetId: string)` → `Promise` — Query token balance - - `listAssets()` → `Promise` — List all owned assets with balances - - `listIncoming()` → `Promise` — List pending token receipts - - `getSpendableTokens(assetId: string)` → `Promise` — Get UTXO list for asset - - `proveOwnership(assetId: string, amount: number, verifierKey: string)` → `Promise` — Create cryptographic proof of token ownership - - `verifyOwnership(proof: OwnershipProof)` → `Promise` — Validate ownership proof - -### Token Encoding/Decoding -- **`BTMSToken`** — Static utility class for token serialization; methods: - - `decode(lockingScriptHex: string)` → `BTMSTokenDecodeResult` — Extract token fields from locking script - - `isIssuance(decoded: DecodedBTMSToken)` → `boolean` — Check if token is an issuance (field 0 === "ISSUE") - - `isValidAssetId(id: string)` → `boolean` — Validate asset ID format (txid.vout) - - `computeAssetId(txid: string, vout: number)` → `string` — Derive canonical asset ID - -### Configuration & Types -- **`BTMSConfig`** — Options object with: - - `wallet?: WalletInterface` — BRC-100 wallet for transactions (defaults to WalletClient()) - - `networkPreset?: 'local' | 'mainnet' | 'testnet'` — Overlay network endpoint selection - - `comms?: CommsLayer` — Optional communications layer (e.g., MessageBoxClient for delivery) - -### Type Definitions -- **`BTMSAsset`** — Asset representation: `{ assetId, name?, balance, metadata?, hasPendingIncoming? }` -- **`BTMSAssetMetadata`** — Asset metadata: `{ name?, description?, iconURL?, [key: string]: unknown }` -- **`IssueResult`** — Issuance outcome: `{ success, txid, assetId, vout, amount, error? }` -- **`SendResult`** — Send outcome: `{ success, txid, tokenForRecipient, changeAmount?, error? }` -- **`AcceptResult`** — Acceptance outcome: `{ success, amount, assetId, error? }` -- **`BurnResult`** — Burn outcome: `{ success, txid, assetId, amountBurned, error? }` -- **`IncomingToken`** — Pending receipt: `{ assetId, amount, from, txid, vout, metadata? }` -- **`BTMSTokenOutput`** — Spendable token UTXO: `{ txid, vout, amount, satoshis, script }` -- **`OwnershipProof`** — Proof of token ownership with signature and amount -- **`ProveOwnershipResult`**, **`VerifyOwnershipResult`** — Proof outcomes - -### Constants -- **`BTMS_TOPIC`** — Topic ID for overlay broadcast -- **`BTMS_LOOKUP_SERVICE`** — Overlay lookup service endpoint -- **`BTMS_PROTOCOL_ID`** — Protocol namespace for discovery -- **`BTMS_LABEL_PREFIX`** — Prefix for wallet action labels -- **`BTMS_BASKET`** — Default basket for token outputs -- **`DEFAULT_TOKEN_SATOSHIS`** — Default satoshi value per token UTXO -- **`ISSUE_MARKER`** — Field 0 value for new issuances: `"ISSUE"` - -### Utilities -- **`parseCustomInstructions(message: string)`** → `ParsedCustomInstructions` — Parse app-specific instructions from envelope - -## Real Usage Patterns - -### 1. Issue a new token -```typescript -import { BTMS } from '@bsv/btms' - -const btms = new BTMS({ networkPreset: 'mainnet' }) - -const result = await btms.issue(1000000, { - name: 'GOLD', - description: 'Represents 1 gram of gold', - iconURL: 'https://example.com/gold.png' -}) - -console.log('Asset ID:', result.assetId) // 'abc123...def.0' -console.log('Tx:', result.txid) -``` - -### 2. Send tokens to recipient -```typescript -const recipientIdentityKey = '03abc123...' // Recipient's public key -const sendResult = await btms.send( - 'abc123...def.0', // Asset ID from issuance - recipientIdentityKey, - 100 // Amount -) - -console.log('Txid:', sendResult.txid) -console.log('Change:', sendResult.changeAmount) -``` - -### 3. Accept incoming token payment -```typescript -const incoming = await btms.listIncoming() -for (const payment of incoming) { - console.log(`Incoming: ${payment.amount} of ${payment.assetId}`) - const result = await btms.accept(payment) - console.log(`Accepted ${result.amount} tokens`) -} -``` - -### 4. Check balance and list assets -```typescript -const balance = await btms.getBalance('abc123...def.0') -console.log('Balance:', balance) - -const assets = await btms.listAssets() -for (const asset of assets) { - console.log(`${asset.name}: ${asset.balance} (${asset.assetId})`) - if (asset.hasPendingIncoming) { - console.log(` (Has pending incoming transfers)`) - } -} -``` - -### 5. Burn tokens -```typescript -// Burn specific amount -const result = await btms.burn('abc123...def.0', 100) -console.log('Burned:', result.amountBurned) - -// Burn entire balance -const resultAll = await btms.burn('abc123...def.0') -``` - -### 6. Prove token ownership (for collateral, escrow, etc.) -```typescript -const verifierKey = '03def456...' // Party to prove to -const proof = await btms.proveOwnership( - 'abc123...def.0', - 500, // Amount to prove - verifierKey -) - -// Send proof to verifier... -// Verifier validates: -const verified = await btms.verifyOwnership(proof) -if (verified.valid) { - console.log(`Verified ${verified.amount} tokens from ${verified.prover}`) -} -``` - -### 7. Decode token from raw locking script -```typescript -import { BTMSToken } from '@bsv/btms' - -const scriptHex = '76a9...' // Token output locking script -const decoded = BTMSToken.decode(scriptHex) - -if (decoded.valid) { - console.log('Asset:', decoded.assetId) - console.log('Amount:', decoded.amount) - console.log('Metadata:', decoded.metadata) - - if (BTMSToken.isIssuance(decoded)) { - console.log('This is a new issuance') - } -} -``` - -## Key Concepts - -- **Asset ID** — Canonical token identifier: `{txid}.{vout}` where output 0 of the issuance tx is the first token mint. All transfers reference this ID. -- **UTXO-based tokens** — Each token lives as a separate output on-chain. Spending a token UTXO is like spending Bitcoin — requires unlocking script from previous owner. -- **PushDrop Schema** — 3-field encoding in locking script: - - Field 0: `"ISSUE"` (new token) or asset ID (transfer) - - Field 1: Amount as UTF-8 string - - Field 2: Metadata as JSON string (optional) -- **Issuance** — When field 0 is `"ISSUE"`, the output is a new token mint. Asset ID becomes the txid and vout after confirmation. -- **Transfer** — When field 0 contains an asset ID, tokens are being moved. TopicManager enforces conservation: output amounts ≤ input amounts. -- **Metadata** — JSON object with asset properties (name, description, icon URL, custom fields). Carried through transfer and burn operations. -- **Incoming Token** — Pending receipt of tokens from another user. Must be explicitly accepted to be added to wallet's balance. -- **Ownership Proof** — Cryptographic proof using key linkage that proves prover owns a given amount of tokens without revealing private key. Used for collateral, escrow, access control. - -## Dependencies - -### Runtime -- **`@bsv/sdk`** (peer dependency) ^2.0.14 — Transaction building, scripting, wallet interface - -### Dev (from package.json) -- **`jest`** ^30.3.0 — Test framework -- **`ts-jest`** ^29.4.9 — TypeScript support for Jest -- **`typescript`** ^5.2.2 — Compiler -- **`oxlint`** ^1.75.0 — Linter -- **`ts2md`** ^0.2.0 — Documentation generator - -### Other ts-stack packages -- **`@bsv/sdk`** — Core dependency (peer dep) -- **`@bsv/wallet-toolbox`** — Used in apps that integrate token signing (not a runtime dep of btms itself) - -## Common Pitfalls / Gotchas - -1. **Asset ID format** — Asset IDs must be in format `{txid}.{vout}` (lowercase hex txid, dot, vout number). Typos will cause asset not found. - -2. **Issuance not confirmed** — Immediately after `issue()`, the asset ID returned uses the pending txid. The canonical asset ID is only guaranteed after the tx is mined. Store results carefully. - -3. **Amount precision** — Token amounts are always positive integers (no decimals). Metadata can store divisibility info (e.g., "8 decimal places means divide by 10^8 for display"), but on-chain amounts are always whole numbers. - -4. **Incoming not auto-accepted** — Received tokens stay in `listIncoming()` until explicitly `accept()`ed. Apps must prompt users or auto-accept based on policy. - -5. **Metadata changes not tracked** — If you transfer tokens with different metadata than the original issuance, the TopicManager may reject the transaction. Always preserve original metadata through transfers. - -6. **Burn is permanent** — Once burned, tokens cannot be recovered. No undo mechanism. Apps should confirm with users before burning. - -7. **Recipient key format** — `send()` requires the recipient's identity public key (33-byte compressed format: `03...` or `02...`). If using a different key format, conversion will fail silently. - -8. **No fractional transfers** — You cannot split a token UTXO into smaller amounts. Each UTXO is atomic. To send 50 of 100 tokens, you must create a new UTXO with 50 and return 50 as change. - -9. **Wallet interface required** — BTMS needs a `WalletInterface` (BRC-100) to create transactions. If no wallet is provided, it defaults to `WalletClient()` which only works in browser/desktop with installed wallet. - -10. **Overlay service latency** — Topic manager confirmation may take seconds to minutes. Apps shouldn't assume tokens are immediately transferable after send succeeds; they may fail spend until overlay confirms. - -11. **Address reuse** — All token operations use derivation paths from the wallet. Address reuse is handled by wallet's key derivation; no action needed from app. - -## Spec Conformance - -- **PushDrop** — 3-field token encoding per spec -- **BTMSTopicManager** — Validates token transactions on overlay -- **BTMSLookupService** — Indexes tokens for discovery -- **BRC-100** — Uses standard wallet interface for signing -- **BRC-98/99** — Permission hooks for wallet integration (via btms-permission-module) - -## File Map - -- **`src/index.ts`** — Public API exports and module factory -- **`src/BTMS.ts`** — Main class with token operations -- **`src/BTMSToken.ts`** — Static encoding/decoding utilities -- **`src/types.ts`** — TypeScript interfaces and type definitions -- **`src/constants.ts`** — Protocol constants (topic, markers, defaults) -- **`src/utils.ts`** — Utility functions (custom instruction parsing) -- **`src/__tests__/`** — Test files (BTMS.test.ts, BTMSToken.test.ts) - -## Integration Points - -- **@bsv/sdk** — Uses Transaction, Script, PushDrop, WalletInterface for all operations -- **@bsv/wallet-toolbox** — Apps using wallet-toolbox for signing can use BTMS directly (wallet is injected) -- **@bsv/btms-permission-module** — Adds BTMS token spend gating via BRC-98/99 hooks in wallet-toolbox -- **Overlay services** — TopicManager validates token invariants; LookupService indexes assets -- **MessageBoxClient** — Optional comms layer for encrypted token delivery between users +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/wallet/ts-wallet-relay/AGENTS.md b/packages/wallet/ts-wallet-relay/AGENTS.md index 8fcd27aa4..dcea67c80 100644 --- a/packages/wallet/ts-wallet-relay/AGENTS.md +++ b/packages/wallet/ts-wallet-relay/AGENTS.md @@ -1,324 +1,10 @@ -# CLAUDE.md — @bsv/wallet-relay v0.1.0 +# ts-stack agent instructions -## Purpose +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -Wallet Relay enables mobile-to-desktop wallet pairing via QR codes and encrypted WebSocket tunnels. A web app (desktop browser) shows a QR code; user scans with their mobile BSV wallet; all wallet operations (signing, key retrieval, etc.) are proxied over HTTPS+WSS relay servers to the mobile without exposing keys or trust chains to the desktop. Provides both the relay server infrastructure (Node.js) and React frontend components for web apps to add "Connect Mobile Wallet" functionality. - -## Public API Surface - -### Server-Side (Node.js) - -From root exports: - -- **`WalletRelayService`** — All-in-one relay server; constructor: `new WalletRelayService(options: WalletRelayServiceOptions)` where options include: - - `app: Express` — Express instance to register routes on - - `server: http.Server` — HTTP server for WebSocket upgrade - - `wallet: WalletLike` — Backend wallet (e.g., ProtoWallet) - - `relayUrl?: string` — Public WebSocket URL (defaults to env.RELAY_URL) - - `origin?: string` — Frontend origin for CORS (defaults to env.ORIGIN) -- Auto-registers three REST routes and WebSocket endpoint: - - `GET /api/session` — Create new pairing session → returns `{ sessionId, qrDataUrl, pairingUri, desktopToken }` - - `GET /api/session/:id` — Poll session status - - `POST /api/request/:id` — Send wallet RPC call to paired mobile (requires X-Desktop-Token header) - - `WS /ws` — WebSocket relay for bidirectional communication - -### Lower-level components: - -- **`QRSessionManager`** — Session lifecycle management (creation, status tracking, cleanup) -- **`WebSocketRelay`** — WebSocket server with message routing, topic validation, token auth -- **`WalletRequestHandler`** — Converts RPC calls to wallet method invocations - -### Client-Side (Browser) — `@bsv/wallet-relay/client` - -- **`WalletRelayClient`** — Direct HTTP/WebSocket client for session management and RPC calls; methods: - - `createSession()` → `Promise<{ sessionId, qrDataUrl, pairingUri, desktopToken }>` - - `getSessionStatus(sessionId)` → `Promise` - - `sendRequest(sessionId, request, desktopToken)` → `Promise` - -### React Components — `@bsv/wallet-relay/react` - -- **`useWalletRelayClient(relayUrl?)`** — Hook wrapping WalletRelayClient; returns client instance -- **`useWalletSession(client, sessionId?)`** — Hook for session state management -- **`WalletConnectionModal`** — Pre-built UI component for QR pairing flow (shows modal, displays QR, handles scanning) -- **`QRDisplay`** — Standalone QR code display component -- **`RequestLog`** — UI for displaying pending/completed RPC requests - -### Shared Types & Utilities - -- **`Session`** — Session state: `{ id, status, qrData, pairingUri, desktopToken, mobileConnected, createdAt }` -- **`SessionStatus`** — Enum: `'pending' | 'paired' | 'disconnected' | 'expired'` -- **`PairingParams`** — QR encoding: `{ relayUrl, sessionId, sessionKey }` -- **`RpcRequest`** — Wallet call: `{ jsonrpc: '2.0', id, method, params }` -- **`RpcResponse`** — Call result: `{ jsonrpc: '2.0', id, result?, error? }` -- **`WireEnvelope`** — Encrypted message: `{ iv, ciphertext, tag, method, sessionId }` -- **`WalletLike`** — Any object implementing core wallet methods (createAction, signAction, etc.) - -### Crypto & Encoding Utilities - -From shared exports (also in `./client`): - -- **`encryptEnvelope(message, key, iv?)`** → `WireEnvelope` — AES-256-GCM encryption -- **`decryptEnvelope(envelope, key)`** → `string` — Decryption -- **`parsePairingUri(uri)`** → `ParseResult` — Extract params from QR URI -- **`buildPairingUri(params)`** → `string` — Construct QR-scannable URI -- **`verifyPairingSignature(sig, message, pubKey)`** → `boolean` — ECDSA signature verification -- **`bytesToBase64url(bytes)`**, **`base64urlToBytes(b64)`** — URL-safe base64 encoding - -### CLI Scaffolding - -- **`npx @bsv/wallet-relay init`** — Command to scaffold Express backend + React frontend wired together - - Options: `--nextjs`, `--backend`, `--frontend`, `--backend-dir`, `--frontend-dir` - -## Real Usage Patterns - -### 1. Set up relay server (Express + Node.js) - -```typescript -import express from 'express' -import { createServer } from 'http' -import cors from 'cors' -import { WalletRelayService } from '@bsv/wallet-relay' -import { ProtoWallet, PrivateKey } from '@bsv/sdk' - -const app = express() -app.use( - cors({ - origin: process.env.ORIGIN, - allowedHeaders: ['Content-Type', 'Authorization', 'X-Desktop-Token'] - }) -) -app.use(express.json()) - -const server = createServer(app) -const wallet = new ProtoWallet(PrivateKey.fromHex(process.env.WALLET_PRIVATE_KEY!)) - -new WalletRelayService({ - app, - server, - wallet, - relayUrl: process.env.RELAY_URL, - origin: process.env.ORIGIN -}) - -server.listen(3000) -``` - -### 2. Create session and get QR code (frontend React) - -```typescript -import { useWalletRelayClient } from '@bsv/wallet-relay/react' -import { useEffect, useState } from 'react' - -function WalletConnection() { - const client = useWalletRelayClient('https://relay.example.com') - const [qrData, setQrData] = useState(null) - const [sessionId, setSessionId] = useState(null) - - useEffect(() => { - const setup = async () => { - const session = await client.createSession() - setSessionId(session.sessionId) - setQrData(session.qrDataUrl) // Base64 PNG - } - setup() - }, []) - - return ( -
- {qrData && Scan to pair wallet} -

Scan with your BSV wallet app

-
- ) -} -``` - -### 3. Use WalletConnectionModal component - -```typescript -import { WalletConnectionModal } from '@bsv/wallet-relay/react' - -function App() { - return ( - <> - { - console.log('Mobile connected, sessionId:', sessionId) - // Send wallet requests to mobile - }} - installUrl="https://desktop.bsvb.tech" - /> - - ) -} -``` - -### 4. Send wallet RPC call from desktop to mobile - -```typescript -import { useWalletRelayClient } from '@bsv/wallet-relay/react' - -function sendPayment(client: WalletRelayClient, sessionId: string, desktopToken: string) { - const request = { - jsonrpc: '2.0', - id: 1, - method: 'createAction', - params: [ - { - description: 'Send payment', - outputs: [ - { - satoshis: 5000, - lockingScript: '76a914...' - } - ] - } - ] - } - - const response = await client.sendRequest(sessionId, request, desktopToken) - - if (response.error) { - console.error('Mobile rejected:', response.error) - } else { - console.log('Action created:', response.result.signableTransaction) - } -} -``` - -### 5. Mobile wallet implementation (WalletPairingSession) - -```typescript -import { WalletPairingSession } from '@bsv/wallet-relay/client' -import { PrivateKey } from '@bsv/sdk' - -const session = new WalletPairingSession({ - baseUrl: 'https://relay.example.com', - identityKey: PrivateKey.fromRandom(), - wallet: myWalletInstance -}) - -// Scan desktop QR code to get pairingUri -const { relayUrl, sessionId, sessionKey } = parsePairingUri(scannedQR) - -// Pair with desktop -await session.pair({ relayUrl, sessionId, sessionKey }) - -// Listen for incoming requests -session.onRequest = async request => { - // Forward to local wallet - const result = await myWallet[request.method](request.params) - return result -} - -// Session handles encryption/decryption automatically -``` - -### 6. Using Next.js with wallet relay - -```typescript -// pages/api/wallet-request.ts -import { WalletRelayClient } from '@bsv/wallet-relay/client' - -export default async function handler(req, res) { - const { sessionId, desktopToken, request } = req.body - - const client = new WalletRelayClient({ - baseUrl: process.env.RELAY_URL - }) - - const response = await client.sendRequest(sessionId, request, desktopToken) - - // Must forward X-Desktop-Token header - res.setHeader('X-Desktop-Token', desktopToken) - res.json(response) -} -``` - -## Key Concepts - -- **QR Pairing** — Desktop displays QR code encoding relay URL + session ID. Mobile scans and establishes WebSocket connection. -- **Encrypted Relay** — All wallet requests/responses are AES-256-GCM encrypted. Relay server never sees plaintext. -- **Stateless Sessions** — Each pairing session is isolated. Multiple desktops can pair to same mobile (each gets own session). -- **Desktop Token** — Opaque token returned from `GET /api/session` and required on `POST /api/request/:id`. Ensures only the frontend that created the session can use it, even if sessionId is leaked. -- **Wallet Method Forwarding** — Desktop sends standard JSON-RPC calls (createAction, signAction, getPublicKey, etc.) to mobile wallet. Mobile executes locally and returns result. -- **No Key Export** — Mobile never exports private keys. Desktop sees only signatures, public keys, and action results. Keys never leave mobile. -- **Session Expiry** — Sessions timeout after inactivity (typically 24 hours). Mobile can pair new desktop anytime. - -## Dependencies - -### Runtime (Peer Deps) - -- **`@bsv/sdk`** ^2.0.14 — Cryptography and wallet types -- **`express`** >=4.0.0 (optional) — Web framework for server -- **`ws`** >=8.0.0 (optional) — WebSocket server (required if using WalletRelayService) -- **`qrcode`** >=1.5.0 (optional) — QR code generation (required if using QR components) -- **`react`** >=17.0.0 (optional) — React (required for react exports) - -### Dev - -- **`jest`** ^30.3.0 — Test runner -- **`ts-jest`** ^29.4.6 — TypeScript support -- **`typescript`** ^5.4.0 — Compiler -- **`esbuild`** ^0.28.1 — Direct ESM/CJS bundler used by `build.mjs` - -### Other ts-stack packages - -- **`@bsv/sdk`** — Cryptography, ProtoWallet, PrivateKey types - -## Common Pitfalls / Gotchas - -1. **Backend key stability** — `PrivateKey` must be the same across server restarts. Store in env var or secure vault, never generate new key each start. - -2. **Missing X-Desktop-Token header** — `POST /api/request/:id` requires `X-Desktop-Token` header. If missing, request is rejected. Browser CORS preflight must allow this header in `allowedHeaders`. - -3. **CORS misconfiguration** — If frontend and backend are different origins, CORS headers must be set. Missing `Access-Control-Allow-Credentials` or `Access-Control-Allow-Headers` will cause browser to block requests. - -4. **Session expiry during long operations** — If user takes too long to approve on mobile (e.g., verifying on hardware wallet), session may expire. Store desktopToken and allow re-pairing for retry. - -5. **QR code data format** — QR must encode a valid pairingUri. If QR library outputs wrong format, mobile won't recognize it. Test with actual mobile wallet before deploying. - -6. **WebSocket TLS mismatch** — If frontend is HTTPS but relay is ws:// (not wss://), browser blocks upgrade. Always use wss:// in production. - -7. **Relay URL in QR** — The relay URL in QR is public. If relay is on internal network, mobile can't reach it. Use publicly routable URL or tunnel. - -8. **Mobile token caching** — Mobile may cache auth tokens. Revoking session on desktop doesn't immediately invalidate mobile connection. Keep sessions short-lived. - -9. **Concurrent requests from same desktop** — Each `POST /api/request/:id` is independent. If desktop sends two requests quickly, mobile may process in unexpected order. Add request IDs for sequencing. - -10. **React hook dependencies** — `useWalletRelayClient` and `useWalletSession` should be used carefully with dependency arrays. Missing dependencies can cause stale closures. - -11. **Scaffold file overwrite** — `npx @bsv/wallet-relay init` does not overwrite existing files. If you run it twice, second run won't re-scaffold. - -12. **Mobile origin header** — In split frontend/backend dev setups, set `MOBILE_ORIGIN` env var so mobile device (on different LAN) can reach backend. Don't set in production. - -## Spec Conformance - -- **ECDH key agreement** — Pairing uses ECDH for shared secret derivation -- **AES-256-GCM** — Authenticated encryption for all message envelopes -- **JSON-RPC 2.0** — Standard format for all wallet method calls -- **WebSocket** — RFC 6455 WebSocket protocol with TLS -- **Base64url** — URL-safe base64 for encoding binary data in URLs - -## File Map - -- **`src/index.ts`** — Server exports (WalletRelayService, WebSocketRelay, etc.) -- **`src/client.ts`** — Client exports (WalletRelayClient, WalletPairingSession, crypto utils) -- **`src/react.tsx`** — React exports (hooks, components) -- **`src/server/`** — Server implementation (QRSessionManager, WebSocketRelay, handlers) -- **`src/client/`** — Client implementation (WalletRelayClient, WalletPairingSession) -- **`src/react/`** — React components (WalletConnectionModal, QRDisplay, RequestLog, hooks) -- **`src/shared/`** — Shared utilities (crypto, encoding, URI parsing, signature verification) -- **`src/types.ts`** — Shared TypeScript types -- **`bin/init.mjs`** — CLI scaffolding command -- **`template/`** — Template files for scaffold -- **`tests/`** — Test files - -## Integration Points - -- **@bsv/sdk** — PrivateKey, ProtoWallet, signature verification -- **Express** — HTTP framework for relay server -- **WebSocket (ws)** — Real-time bidirectional communication -- **React** — UI components for pairing flow -- **QRCode library** — Visual QR generation -- Custom wallet apps — Implement `WalletLike` interface to use as relay backend +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/wallet/wallet-toolbox-examples/AGENTS.md b/packages/wallet/wallet-toolbox-examples/AGENTS.md new file mode 100644 index 000000000..dcea67c80 --- /dev/null +++ b/packages/wallet/wallet-toolbox-examples/AGENTS.md @@ -0,0 +1,10 @@ +# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/wallet/wallet-toolbox-examples/README.md b/packages/wallet/wallet-toolbox-examples/README.md index 742927a36..4cfb5fcc6 100644 --- a/packages/wallet/wallet-toolbox-examples/README.md +++ b/packages/wallet/wallet-toolbox-examples/README.md @@ -39,7 +39,8 @@ We're always looking for contributors to help us improve the SDK. Whether it's b 4. **Test**: Run `pnpm --filter @bsv/wallet-toolbox-examples test`. 5. **Commit**: Commit your changes and push to your fork. 6. **Pull Request**: Open a pull request from your fork to this repository. - For more details, check the [contribution guidelines](./CONTRIBUTING.md). + For more details, check the + [repository contribution guidelines](https://github.com/bsv-blockchain/ts-stack/blob/main/CONTRIBUTING.md). ## Support & Contacts diff --git a/packages/wallet/wallet-toolbox/.github/ISSUE_TEMPLATE/bug_report.md b/packages/wallet/wallet-toolbox/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 4fc56f13f..000000000 --- a/packages/wallet/wallet-toolbox/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: Bug Report -about: Report a bug or an issue you've found with `@bsv/sdk`. -title: "[BUG]" -labels: bug -assignees: '' - ---- - -## Bug Description - -Briefly describe the bug/issue you've encountered. - -## Steps to Reproduce - -1. Step 1 -2. Step 2 -3. ... - -## Expected Behavior - -What should have happened if the bug hadn't occurred? - -## Actual Behavior - -What actually happened? - -## Stack Traces or Screenshots - -If applicable, add screenshots or stack traces to help explain the issue. - -## Environment - -- OS: [e.g. MacOS, Windows] -- Node version: [e.g. 12.20] -- `@bsv/sdk` version: [e.g. 1.2.3] - -## Additional Information - -Provide any additional context or information about the bug. diff --git a/packages/wallet/wallet-toolbox/.github/ISSUE_TEMPLATE/discussion.md b/packages/wallet/wallet-toolbox/.github/ISSUE_TEMPLATE/discussion.md deleted file mode 100644 index 52aa083ce..000000000 --- a/packages/wallet/wallet-toolbox/.github/ISSUE_TEMPLATE/discussion.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Discussion -about: Propose a discussion or seek clarification about a feature or topic. -title: "[DISCUSSION]" -labels: discussion -assignees: '' - ---- - -## Summary - -Briefly describe the topic you'd like to discuss. - -## Motivation - -Why do you believe this to be important? - -## Description - -Provide a detailed description or elaborate on your topic. - -## Additional References - -Provide any additional articles, links, or context that would help facilitate the discussion. diff --git a/packages/wallet/wallet-toolbox/.github/pull_request_template.md b/packages/wallet/wallet-toolbox/.github/pull_request_template.md deleted file mode 100644 index b8bf7cb20..000000000 --- a/packages/wallet/wallet-toolbox/.github/pull_request_template.md +++ /dev/null @@ -1,22 +0,0 @@ -## Description of Changes - -Provide a brief description of the changes you've made. - -## Linked Issues / Tickets - -Reference any related issues or tickets, e.g. "Closes #123". - -## Testing Procedure - -Describe the tests you've added or any testing steps you've taken. - -- [ ] I have added new unit tests -- [ ] All tests pass locally -- [ ] I have tested manually in my local environment - -## Checklist: - -- [ ] I have performed a self-review of my own code -- [ ] I have made corresponding changes to the documentation -- [ ] I have run `npm run doc` and `npm run lint` one final time before requesting a review -- [ ] I have run `npm version patch` so that my changes will trigger a new version to be released when they are merged \ No newline at end of file diff --git a/packages/wallet/wallet-toolbox/.github/workflows/push.yaml b/packages/wallet/wallet-toolbox/.github/workflows/push.yaml deleted file mode 100644 index d389903de..000000000 --- a/packages/wallet/wallet-toolbox/.github/workflows/push.yaml +++ /dev/null @@ -1,146 +0,0 @@ -name: Test and Publish Package - -on: - push: - branches: - - master - - main - pull_request: - -jobs: - test: - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - - - run: npm ci --ignore-scripts - - run: npm run build --if-present - - run: npm test - - publish: - runs-on: ubuntu-latest - permissions: - id-token: write - contents: read - needs: test - # Only run on push to master/main, not on PRs - if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main') - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '24' - registry-url: 'https://registry.npmjs.org' - always-auth: false - - - run: npm ci --ignore-scripts - - run: npm run build --if-present - - - name: Sync client version with root - run: | - ROOT_VERSION=$(node -p "require('./package.json').version") - cd client - npm version $ROOT_VERSION --no-git-tag-version --allow-same-version - cd .. - - - name: Sync mobile version with root - run: | - ROOT_VERSION=$(node -p "require('./package.json').version") - cd mobile - npm version $ROOT_VERSION --no-git-tag-version --allow-same-version - cd .. - - - name: Check if root version already published - id: check-root-version - run: | - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Root Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Root version $PACKAGE_VERSION already published" - echo "should_publish_root=false" >> $GITHUB_OUTPUT - else - echo "Root version $PACKAGE_VERSION not yet published" - echo "should_publish_root=true" >> $GITHUB_OUTPUT - fi - - - name: Publish root to npm - if: steps.check-root-version.outputs.should_publish_root == 'true' - run: npm publish - - - name: Skip publishing root - if: steps.check-root-version.outputs.should_publish_root == 'false' - run: echo "Skipping publish root - version already exists on npm" - - - name: Check if client version already published - id: check-client-version - run: | - cd client - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Client Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Client version $PACKAGE_VERSION already published" - echo "should_publish_client=false" >> $GITHUB_OUTPUT - else - echo "Client version $PACKAGE_VERSION not yet published" - echo "should_publish_client=true" >> $GITHUB_OUTPUT - fi - cd .. - - - name: Publish client to npm - if: steps.check-client-version.outputs.should_publish_client == 'true' - run: | - cd client - npm publish - cd .. - - - name: Skip publishing client - if: steps.check-client-version.outputs.should_publish_client == 'false' - run: echo "Skipping publish client - version already exists on npm" - - - name: Check if mobile version already published - id: check-mobile-version - run: | - cd mobile - PACKAGE_NAME=$(node -p "require('./package.json').name") - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - echo "Mobile Package: $PACKAGE_NAME@$PACKAGE_VERSION" - - # Check if this version exists on npm - if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then - echo "Mobile version $PACKAGE_VERSION already published" - echo "should_publish_mobile=false" >> $GITHUB_OUTPUT - else - echo "Mobile version $PACKAGE_VERSION not yet published" - echo "should_publish_mobile=true" >> $GITHUB_OUTPUT - fi - cd .. - - - name: Publish mobile to npm - if: steps.check-mobile-version.outputs.should_publish_mobile == 'true' - run: | - cd mobile - npm publish - cd .. - - - name: Skip publishing mobile - if: steps.check-mobile-version.outputs.should_publish_mobile == 'false' - run: echo "Skipping publish mobile - version already exists on npm" \ No newline at end of file diff --git a/packages/wallet/wallet-toolbox/AGENTS.md b/packages/wallet/wallet-toolbox/AGENTS.md index ccf9403fc..dcea67c80 100644 --- a/packages/wallet/wallet-toolbox/AGENTS.md +++ b/packages/wallet/wallet-toolbox/AGENTS.md @@ -1,301 +1,10 @@ -# CLAUDE.md — @bsv/wallet-toolbox v2.1.24 +# ts-stack agent instructions -## Purpose -The Wallet Toolbox is the reference implementation of the BRC-100 wallet standard. It connects the SDK's cryptographic primitives to real storage backends (SQLite, MySQL, IndexedDB), network services (ARC, WhatsOnChain, Chaintracks), and signing flows to provide a complete, production-ready wallet that developers can use directly or customize for their own wallet apps (like BSV Desktop or BSV Browser). +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. -## Public API Surface - -### Main Setup Exports -- **`SetupWallet(config?)`** — Factory function returning a fully initialized `Wallet` instance with sensible defaults; config includes `env: 'main' | 'test'`, `endpointUrl`, `chain` -- **`Setup(options)`** — Advanced configuration factory with explicit storage, services, and key manager injection -- **`SetupClient(options)`** — Browser-only setup excluding Node.js storage backends (Knex/SQLite/MySQL) -- **`SetupWallet(config)`** — Convenience wrapper around `Setup` - -### Core Wallet Class -- **`Wallet`** — Main BRC-100 wallet implementation; methods: - - `createAction(args: CreateActionArgs)` — Create transaction from outputs; returns signable transaction reference - - `signAction(args: SignActionArgs)` — Sign a created action; returns signature(s) - - `listActions(args: ListActionsArgs)` — Query transaction history - - `listOutputs(args: ListOutputsArgs)` — Query UTXO state and spent history - - `internalizeAction(args)` — Import external transaction into wallet tracking - - `relinquishOutput(args)` — Mark output as no longer spendable (e.g., for dust) - - `getPublicKey(args)` — Retrieve identity or derivation key - - `listCertificates()` — List installed certificates for peer auth - - `proveCertificate(args)` — Generate certificate proof - - `discoverByTopic(args)` — Resolve identity/protocol from overlay topics - - `listPermissions(args)` — Query permission grants by app - - `submitDirectTransaction(tx)` — Bypass action framework for raw tx submission (advanced) - -### Storage Layer -From `storage/` — pluggable backends: -- **`KnexWalletStorage`** — SQL backend (SQLite, MySQL, PostgreSQL) via Knex query builder -- **`IndexedDBWalletStorage`** — Browser IndexedDB (client and mobile builds) -- **`RemoteWalletStorage`** — HTTP client for remote server (client/server via HTTPS) -- **`StorageProvider`** — Factory pattern for storage selection -- All implement: `WalletStorage` interface with methods for actions, outputs, certificates, permissions - -### Services Layer -From `services/` — network integration: -- **`Services`** — Aggregated network services container; includes: - - `broadcaster: Broadcaster` — Transaction submission (ARC, WhatsOnChain, etc.) - - `chainTracker: ChainTracker` — Block header and UTXO queries - - `merkleVerifier: MerkleVerifier` — SPV proof validation - - `lookupService: LookupService` — Overlay service resolution -- **`ArcSSEClient`** — Server-sent events for real-time ARC transaction status -- Service providers for mainnet, testnet, local - -### Monitor Daemon -From `monitor/` — background transaction tracking: -- **`Monitor`** — Long-running service that: - - Polls pending transactions for confirmation - - Detects chain reorganizations - - Acquires merkle proofs - - Rebroadcasts stalled transactions - - Updates wallet state automatically -- Methods: `start()`, `stop()`, `startTasks()`, `stopTasks()` -- Runs independently or integrated into `Wallet` - -### Key Management -- **`PrivilegedKeyManager`** — Secure key storage with: - - Shamir secret sharing for multi-party recovery - - Obfuscation of root keys in storage - - Protocol-based key derivation (BRC-42/43) -- **`SimpleWalletManager`** — Lighter-weight key manager for testing/development -- **`ShamirWalletManager`** — Production key manager with advanced secret sharing -- **`CWIStyleWalletManager`** — Key manager for CWI-style wallets - -### Permission Management -- **`WalletPermissionsManager`** — Fine-grained per-app, per-protocol permissions: - - Tracks which apps can do what (e.g., "App A can spend tokens from Protocol X") - - Supports grouped approval flows (ask once for multiple related operations) - - Integration hooks for permission modules (e.g., BTMS token module) -- Methods: `requestPermission()`, `grantPermission()`, `revokePermission()`, `listPermissions()` - -### Signing Integration -- **`WalletSigner`** — Adapter bridging BRC-100 wallet to SDK's `Transaction.sign()` interface - - Converts wallet interface to SDK substrate for transparent signing - - Handles multi-signature orchestration - -### Utilities -From `utility/`: -- **`MessageSigner`** — BRC-18 signed message support -- **`OutputTracker`** — UTXO state machine with spent/unspent/locked transitions -- **`CertificateManager`** — X.509-like certificate storage and retrieval -- **`WalletLogger`** — Structured logging for debugging -- **`EntropyCollector`** — Browser mouse/touch entropy for CSPRNG seeding - -### MockChain -From `mockchain/` — in-memory blockchain for testing: -- **`MockChain`** — Simulated blockchain with: - - `mine()` — Create new block with pending transactions - - `broadcast(tx)` — Submit transaction - - `getUTXOs(address)` — Query outputs - - Full merkle proof generation without network -- Used in test suites; allows offline wallet testing - -### Specialized Modules -- **`WalletAuthenticationManager`** — User login/2FA -- **`WalletSettingsManager`** — Wallet configuration and preferences -- **`WABClient`** — Web App Bridge client for secure wallet communication -- **`EntropyCollector`** — Gathers entropy in browser for key generation - -## Real Usage Patterns - -### 1. Create wallet with default SQLite storage (Node.js) -```typescript -import { SetupWallet } from '@bsv/wallet-toolbox' - -const wallet = await SetupWallet({ - env: 'main' // mainnet -}) - -// Ready to use immediately -const action = await wallet.createAction({ - description: 'Send payment', - outputs: [{ - satoshis: 5000, - lockingScript: '76a914...', - outputDescription: 'payment' - }] -}) -``` - -### 2. Browser wallet with IndexedDB -```typescript -import { SetupClient } from '@bsv/wallet-toolbox' - -const wallet = await SetupClient({ - env: 'test', // testnet - storageProvider: 'indexeddb' -}) - -const utxos = await wallet.listOutputs({ - includeSpent: false, - basket: 'default' -}) -``` - -### 3. Remote wallet (client/server with HTTPS) -```typescript -const wallet = await SetupClient({ - endpointUrl: 'https://wallet-server.example.com', - storageProvider: 'remote' -}) - -// All wallet calls go over HTTPS; keys stay server-side -``` - -### 4. Full setup with custom key manager and services -```typescript -import { Setup, PrivilegedKeyManager, Services } from '@bsv/wallet-toolbox' - -const keyManager = new PrivilegedKeyManager({ - rootKeyHex: '...', // from secure storage - passwordHash: '...' -}) - -const wallet = await Setup({ - keyManager, - services: await Services.build('main'), - storage: await KnexWalletStorage.build({ - client: 'sqlite3', - filename: './wallet.db' - }) -}) -``` - -### 5. Integrate BTMS token permissions -```typescript -import { WalletPermissionsManager } from '@bsv/wallet-toolbox' -import { createBtmsModule } from '@bsv/btms-permission-module' - -const permissionsManager = new WalletPermissionsManager(wallet, appOrigin, { - permissionModules: { - btms: createBtmsModule({ wallet, promptHandler: myUIPromptFunction }) - } -}) - -// Now BTMS token operations are gated by permissions -``` - -### 6. Monitor for transaction confirmations -```typescript -const monitor = new Monitor(wallet.storage, wallet.services, { - pollIntervalMs: 10000 // Check every 10 seconds -}) - -await monitor.startTasks() - -// Monitor will automatically: -// - Detect confirmations -// - Acquire merkle proofs -// - Rebroadcast failed txs -// - Update wallet state -``` - -### 7. Sign transaction via wallet -```typescript -import { WalletSigner } from '@bsv/wallet-toolbox' -import { Transaction } from '@bsv/sdk' - -const signer = new WalletSigner(wallet) - -const tx = new Transaction() -// ... add inputs/outputs ... - -await tx.sign([signer]) // Use wallet for signing -const broadcastResp = await tx.broadcast() -``` - -## Key Concepts - -- **BRC-100 Wallet Interface** — Standardized interface that all wallet implementations follow. Enables apps to work with any wallet (BSV Desktop, BSV Browser, custom wallets, hardware wallets) without code changes. -- **Action** — High-level transaction intent created by app. Wallet converts to specific inputs/outputs. Allows for privacy (app doesn't see which UTXOs used) and flexibility (wallet picks best coins). -- **SignableTransaction** — Wallet's opaque reference to a created action. App requests wallet to sign this reference; wallet does the actual ECDSA signing. -- **Certificate** — P2P authentication proof. Identity key + signature over challenge. Used for peer-to-peer overlay protocols. -- **Protocol** — Namespace for overlay services (e.g., "BTMS" tokens, "Document Registry", custom apps). Each protocol has its own permissions, topics, and discovery. -- **Storage Backend** — Pluggable abstraction. Same `Wallet` code works with SQLite (Node.js), IndexedDB (browser), or remote HTTPS server without code changes. -- **Chain Tracker** — Maintains blockchain state (headers, confirmed height). Enables SPV-based transaction verification without full node. -- **Monitor** — Background daemon that polls wallets' own transactions and updates confirmed status without app polling. -- **Key Derivation** — BRC-42/43 protocol-based hierarchical key generation. Each protocol can request keys from a single root without exposing the root. - -## Dependencies - -### Runtime (from package.json) -- **`@bsv/sdk`** ^2.0.14 — Crypto primitives and transaction library -- **`@bsv/auth-express-middleware`** ^2.0.5 — Auth for Express routes -- **`@bsv/payment-express-middleware`** ^2.0.2 — Payment handling middleware -- **`better-sqlite3`** ^12.6.2 — SQLite engine for Node.js storage -- **`express`** ^4.21.2 — Web framework for server routes (optional for client builds) -- **`hash-wasm`** ^4.12.0 — Cryptographic hashing -- **`idb`** ^8.0.2 — IndexedDB wrapper for browser storage -- **`knex`** ^3.1.0 — SQL query builder for database abstraction -- **`mysql2`** ^3.12.0 — MySQL driver for database storage -- **`ws`** ^8.18.3 — WebSocket for relay and overlay connections - -### Peer Deps -- None explicitly, but `express` is runtime-required for server features - -### Other ts-stack packages -- **`@bsv/sdk`** — Core crypto and transactions -- (Others in wallet family are peer deps, not runtime deps) - -## Common Pitfalls / Gotchas - -1. **Storage backend mismatch** — `SetupClient` excludes SQLite/MySQL. Don't try to use Knex in browser builds; it will fail at runtime. Use IndexedDB or RemoteWalletStorage for client/mobile. - -2. **Wallet state consistency** — If monitor is not running, wallet won't know about confirmations. Apps must either run monitor or poll `listActions()` manually. - -3. **Key manager initialization order** — `PrivilegedKeyManager` must be initialized before `Setup`. Initializing after will cause signing to fail silently. - -4. **Permission denial vs. network error** — When an app is denied permission, the wallet may reject the action. Apps should distinguish between permission denials and actual errors. - -5. **Monitor task conflicts** — Don't run multiple monitor instances on the same storage simultaneously; they will race and cause state corruption. Use a single monitor per wallet. - -6. **CORS on RemoteWalletStorage** — If using HTTP remote storage from browser, the server must have CORS headers set correctly. `withCredentials: true` is used; server must respond with `Access-Control-Allow-Credentials: true`. - -7. **Action reference lifetime** — `SignableTransaction.reference` is valid only for a short window. Don't cache references; create a new action and sign immediately. - -8. **Fee estimation timing** — Action creation doesn't include fees by default. Call `wallet.estimateFee()` separately or let the wallet estimate during signing. - -9. **Output reuse in multi-sig** — If same output is used in multiple pending actions, only one will succeed. Wallet should prevent this, but apps shouldn't rely on it. - -10. **Certificate expiry** — Certificates have expiration. Check `cert.expires` before using for peer authentication. Expired certs are silently rejected. - -11. **Entropy in browser** — `EntropyCollector` requires user interaction (mouse/touch) to work. In headless tests, provide seed entropy manually. - -12. **IDB quota limits** — Browser IndexedDB has quotas (typically GB range). Large wallets with many transactions may hit limits. Implement archiving or use RemoteWalletStorage for scale. - -## Spec Conformance - -- **BRC-100** — Wallet interface standard (full implementation) -- **BRC-18** — Signed messages support -- **BRC-42, BRC-43** — Key derivation protocols -- **BRC-62** — BEEF transaction envelope support -- **SPV** — Full merkle proof verification -- **Bitcoin Script** — Full script evaluation - -## File Map - -- **`src/Wallet.ts`** — Main BRC-100 wallet class -- **`src/Setup.ts`**, **`src/SetupWallet.ts`**, **`src/SetupClient.ts`** — Initialization factories -- **`src/storage/`** — Storage backends (KnexWalletStorage, IndexedDBWalletStorage, RemoteWalletStorage) -- **`src/services/`** — Network services (ARC, WhatsOnChain, Chaintracks integration) -- **`src/monitor/`** — Transaction monitoring daemon -- **`src/sdk/`** — Key managers (PrivilegedKeyManager, SimpleWalletManager, etc.) -- **`src/signer/`** — WalletSigner adapter -- **`src/utility/`** — Helpers (logging, entropy, output tracking) -- **`src/mockchain/`** — In-memory blockchain for testing -- **`src/WalletPermissionsManager.ts`** — Permission system -- **`src/WalletLogger.ts`** — Structured logging -- **`src/entropy/EntropyCollector.ts`** — Browser entropy collection - -## Integration Points - -- **@bsv/sdk** — Uses SDK Transaction, PrivateKey, Script, all crypto -- **@bsv/btms-permission-module** — BTMS tokens register as permission module; wallet-toolbox hosts the permission manager -- **@bsv/wallet-relay** — Mobile wallet pairing uses wallet-toolbox as the wallet backend -- **Express middleware** — auth-express-middleware and payment-express-middleware authenticate and validate requests -- **Database drivers** — Knex abstracts SQLite (better-sqlite3) and MySQL (mysql2) -- **ARC/WhatsOnChain/Chaintracks** — Network service integrations via HTTP -- **Browser APIs** — IndexedDBWalletStorage uses IndexedDB API; EntropyCollector uses mouse/touch events +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/wallet/wallet-toolbox/CHANGELOG.md b/packages/wallet/wallet-toolbox/CHANGELOG.md index 0ea72ef92..95d40d9ce 100644 --- a/packages/wallet/wallet-toolbox/CHANGELOG.md +++ b/packages/wallet/wallet-toolbox/CHANGELOG.md @@ -6,6 +6,9 @@ attention to changes that materially alter behavior or extend functionality. ## wallet-toolbox (unreleased) +- Point Wallet Toolbox contributors and AI agents to the canonical stack-level + contribution and quality policy; keep client and mobile candidate versions + in release lockstep without changing runtime behavior. - Harden storage, action-batch, remoting, WAB, authentication, chaintracker, and monitor implementation paths while preserving persisted schemas, positional call compatibility, and public wallet/storage contracts. diff --git a/packages/wallet/wallet-toolbox/CONTRIBUTING.md b/packages/wallet/wallet-toolbox/CONTRIBUTING.md deleted file mode 100644 index 4d37ce0b4..000000000 --- a/packages/wallet/wallet-toolbox/CONTRIBUTING.md +++ /dev/null @@ -1,89 +0,0 @@ -# CONTRIBUTING to `@bsv/wallet-toolbox` - -Thank you for considering contributing to the BSV Blockchain Libraries Project! This document outlines the processes and practices we expect contributors to adhere to. - -## Table of Contents - -- [CONTRIBUTING to `@bsv/wallet-toolbox`](#contributing-to-bsvwallet-toolbox) - - [Table of Contents](#table-of-contents) - - [General Guidelines](#general-guidelines) - - [Code of Conduct](#code-of-conduct) - - [Posting Issues and Comments](#posting-issues-and-comments) - - [Coding and PRs](#coding-and-prs) - - [Getting Started](#getting-started) - - [Pull Request Process](#pull-request-process) - - [Coding Conventions](#coding-conventions) - - [Documentation and Testing](#documentation-and-testing) - - [Contact \& Support](#contact--support) - -## General Guidelines - -- **Issues First**: If you're planning to add a new feature or change existing behavior, please open an issue first. This allows us to avoid multiple people working on similar features and provides a place for discussion. - -- **Stay Updated**: Always pull the latest changes from the main branch before creating a new branch or starting on new code. - -- **Simplicity Over Complexity**: Your solution should be as simple as possible, given the requirements. - -## Code of Conduct - -### Posting Issues and Comments - -- **Be Respectful**: Everyone is here to help and grow. Avoid any language that might be considered rude or offensive. - -- **Be Clear and Concise**: Always be clear about what you're suggesting or reporting. If an issue is related to a particular piece of code or a specific error message, include that in your comment. - -- **Stay On Topic**: Keep the conversation relevant to the issue at hand. If you have a new idea or unrelated question, please open a new issue. - -### Coding and PRs - -- **Stay Professional**: Avoid including "fun" code, comments, or irrelevant file changes in your commits and pull requests. - -## Getting Started - -1. **Fork the Repository**: Click on the "Fork" button at the top-right corner of this repository. - -2. **Clone the Forked Repository**: `git clone https://github.com/YOUR_USERNAME/wallet-toolbox.git` - -3. **Navigate to the Directory**: `cd wallet-toolbox` - -4. **Install Dependencies**: Since we maintain all code within this library, there shouldn't be any runtime dependencies. Nevertheless, always run `npm install` after pulling to ensure tooling is up to date. - -## Pull Request Process - -1. **Create a Branch**: For every new feature or bugfix, create a new branch. - -2. **Commit Your Changes**: Make your changes and commit them. Commit messages should be clear and concise to explain what was done. - -3. **Run Tests**: Ensure all tests pass using Jest: `npm test`. - -4. **Documentation**: All code must be fully annotated with comments. Update the documentation by running `npm run doc` before creating a pull request. - -5. **Push to Your Fork**: `git push origin your-new-branch`. - -6. **Open a Pull Request**: Go to your fork on GitHub and click "New Pull Request". Fill out the PR template, explaining your changes. - -7. **Code Review**: At least two maintainers must review and approve the PR before it's merged. Address any feedback or changes requested. - -8. **Merge**: Once approved, the PR will be merged into the main branch. - -## Coding Conventions - -- **Code Style**: We use `Oxlint` for our TypeScript coding style. Run `npm run lint` to ensure your code adheres to this style. - -- **No Runtime Dependencies**: All code should be maintained within this library. Do not introduce external dependencies. - -- **Testing**: Always include tests for new code or changes. We aim for industry-standard levels of test coverage. - -- **Documentation**: All functions, classes, and modules should be documented. Use annotation comments to describe the purpose, parameters, and return values. - -## Documentation and Testing - -- **Documentation**: Update the documentation whenever you add or modify the code. Run `npm run doc` to generate the latest docs. - -- **Testing**: We use Jest for all tests. Write comprehensive tests, ensuring edge cases are covered. All PRs should maintain or improve the current test coverage. - -## Contact & Support - -If you have any questions or need assistance with your contributions, feel free to reach out. Remember, we're here to help each other grow and improve the `@bsv/wallet-toolbox`. - -Thank you for being a part of this journey. Your contributions help shape the future of the BSV Blockchain Libraries Project! diff --git a/packages/wallet/wallet-toolbox/README.md b/packages/wallet/wallet-toolbox/README.md index e0d4d8078..f3d364a56 100644 --- a/packages/wallet/wallet-toolbox/README.md +++ b/packages/wallet/wallet-toolbox/README.md @@ -204,7 +204,9 @@ We welcome bug reports, feature requests, and pull requests. 4. Make your changes and run the relevant package checks above 5. Open a pull request -See [CONTRIBUTING.md](./CONTRIBUTING.md) for full guidelines. +See the +[repository contribution guidelines](https://github.com/bsv-blockchain/ts-stack/blob/main/CONTRIBUTING.md) +for the full stack-wide policy. ## Contributors diff --git a/packages/wallet/wallet-toolbox/client/AGENTS.md b/packages/wallet/wallet-toolbox/client/AGENTS.md new file mode 100644 index 000000000..c68ce63e4 --- /dev/null +++ b/packages/wallet/wallet-toolbox/client/AGENTS.md @@ -0,0 +1,10 @@ +# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](../../../../AGENTS.md) +and [contribution policy](../../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/wallet/wallet-toolbox/client/package.json b/packages/wallet/wallet-toolbox/client/package.json index 4ad1af44c..97bcb7137 100644 --- a/packages/wallet/wallet-toolbox/client/package.json +++ b/packages/wallet/wallet-toolbox/client/package.json @@ -1,6 +1,6 @@ { "name": "@bsv/wallet-toolbox-client", - "version": "2.4.19", + "version": "2.4.20", "type": "module", "sideEffects": false, "engines": { diff --git a/packages/wallet/wallet-toolbox/mobile/AGENTS.md b/packages/wallet/wallet-toolbox/mobile/AGENTS.md new file mode 100644 index 000000000..c68ce63e4 --- /dev/null +++ b/packages/wallet/wallet-toolbox/mobile/AGENTS.md @@ -0,0 +1,10 @@ +# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](../../../../AGENTS.md) +and [contribution policy](../../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, `docs/`, +`specs/`, or the applicable operator guide, and propose shared policy at the +repository root. diff --git a/packages/wallet/wallet-toolbox/mobile/package.json b/packages/wallet/wallet-toolbox/mobile/package.json index ebe50033b..eb96aefb5 100644 --- a/packages/wallet/wallet-toolbox/mobile/package.json +++ b/packages/wallet/wallet-toolbox/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@bsv/wallet-toolbox-mobile", - "version": "2.4.19", + "version": "2.4.20", "type": "module", "sideEffects": false, "engines": { diff --git a/packages/wallet/wallet-toolbox/package.json b/packages/wallet/wallet-toolbox/package.json index fe5134867..a8ad22831 100644 --- a/packages/wallet/wallet-toolbox/package.json +++ b/packages/wallet/wallet-toolbox/package.json @@ -1,6 +1,6 @@ { "name": "@bsv/wallet-toolbox", - "version": "2.4.19", + "version": "2.4.20", "sideEffects": false, "type": "commonjs", "engines": { diff --git a/packages/wallet/wallet-toolbox/src/storage/__test/StorageIdb.test.ts b/packages/wallet/wallet-toolbox/src/storage/__test/StorageIdb.test.ts index 83f0bf778..978effad8 100644 --- a/packages/wallet/wallet-toolbox/src/storage/__test/StorageIdb.test.ts +++ b/packages/wallet/wallet-toolbox/src/storage/__test/StorageIdb.test.ts @@ -138,7 +138,7 @@ describe('StorageIdb tests', () => { }) test.skip('1', async () => { - // Tracker #324 final-QA item; owner: wallet-maintainers; review by 2026-10-27. + // Final QA issue #400; owner: wallet-maintainers; review by 2026-10-27. // The isolated case passes but the full suite retains an IndexedDB handle. if (Setup.noEnv('test')) return const env = Setup.getEnv('test') diff --git a/plans/GOVERNANCE.md b/plans/GOVERNANCE.md index b3e684810..7ce8d898a 100644 --- a/plans/GOVERNANCE.md +++ b/plans/GOVERNANCE.md @@ -2,9 +2,9 @@ > **Historical planning record — superseded.** This document describes an > unadopted cross-language governance model and references files and roles that -> are not current repository policy. Use [tracker #324](https://github.com/bsv-blockchain/ts-stack/issues/324), -> `docs/about/contributing.md`, the `governance/` registries, and enforced -> repository rules as the current sources of truth. +> are not current repository policy. Use root `CONTRIBUTING.md`, `AGENTS.md`, +> the `governance/` registries, specifications, and enforced repository rules +> as the current sources of truth. ## Mission diff --git a/plans/HUMAN_QUESTION.md b/plans/HUMAN_QUESTION.md index f6781a76b..b88b6b5c6 100644 --- a/plans/HUMAN_QUESTION.md +++ b/plans/HUMAN_QUESTION.md @@ -2,8 +2,9 @@ > **Resolved archive.** The answers below informed the documentation repair > program. They are preserved as decision history and are not an active task -> list. Current facts are generated or verified by repository CI and remaining -> work is tracked in [#324](https://github.com/bsv-blockchain/ts-stack/issues/324). +> list. Current facts are generated or verified by repository CI; remaining +> final QA is tracked in +> [#400](https://github.com/bsv-blockchain/ts-stack/issues/400). ## Package Organization and Counts - The top-level README.md claims 35 packages + conformance across 7 domains, with a detailed map. docs/packages/index.md claims 27 production-ready. Which count and structure should be canonical? Should we consolidate into one source of truth? diff --git a/plans/README.md b/plans/README.md index 16fca5bb8..aa28f2ea1 100644 --- a/plans/README.md +++ b/plans/README.md @@ -5,10 +5,12 @@ history. They are not automatically current policy. Current repository policy and work are governed by: -- [tracker #324](https://github.com/bsv-blockchain/ts-stack/issues/324); -- `docs/about/contributing.md` and `.github/SECURITY.md`; +- root `CONTRIBUTING.md`, `AGENTS.md`, and `.github/SECURITY.md`; - `governance/` machine-readable registries and repository CI; and -- package and infrastructure operator guides. +- package and infrastructure operator guides. Focused remaining work lives in + [QA #400](https://github.com/bsv-blockchain/ts-stack/issues/400), + [release/assurance #401](https://github.com/bsv-blockchain/ts-stack/issues/401), + and [analysis/CI #402](https://github.com/bsv-blockchain/ts-stack/issues/402). Each planning record must say whether it is historical, resolved, or still a proposal. A proposal requires a current issue, compatibility and security diff --git a/plans/RELIABILITY.md b/plans/RELIABILITY.md index 4a6e7a881..04d9751ff 100644 --- a/plans/RELIABILITY.md +++ b/plans/RELIABILITY.md @@ -3,9 +3,9 @@ > **Historical planning model — not current package status.** The repository > does not presently assign authoritative RL values to every package. Use > generated health/facts reports, CI, package documentation, and -> [tracker #324](https://github.com/bsv-blockchain/ts-stack/issues/324) for -> current evidence and remaining work. RL labels below remain a useful proposed -> vocabulary only. +> [final QA issue #400](https://github.com/bsv-blockchain/ts-stack/issues/400) +> for remaining validation. RL labels below remain a useful proposed vocabulary +> only. The proposed model assigned every package a Reliability Level (RL) in a `BASELINE.md`. An RL would describe current evidence, not a target. diff --git a/scripts/container-supply-chain.test.mjs b/scripts/container-supply-chain.test.mjs index 2204b12af..f6666f51b 100644 --- a/scripts/container-supply-chain.test.mjs +++ b/scripts/container-supply-chain.test.mjs @@ -103,7 +103,9 @@ test('container registry exactly owns every release Dockerfile and immutable bas }) test('checked-in deployment examples use immutable non-latest image references', () => { - const deploymentFiles = trackedFiles('infra/**/*.yml', 'infra/**/*.yaml') + const deploymentFiles = trackedFiles('infra/**/*.yml', 'infra/**/*.yaml').filter( + path => !path.includes('/.github/') + ) for (const path of deploymentFiles) { const imageLines = readRepositoryFile(path) diff --git a/scripts/contributor-policy.mjs b/scripts/contributor-policy.mjs new file mode 100644 index 000000000..5419ff626 --- /dev/null +++ b/scripts/contributor-policy.mjs @@ -0,0 +1,279 @@ +#!/usr/bin/env node + +import fs from 'node:fs' +import path from 'node:path' +import process from 'node:process' +import { fileURLToPath, pathToFileURL } from 'node:url' + +export const REPOSITORY_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') + +const SKIPPED_DIRECTORIES = new Set([ + '.git', + '.next', + '.pagefind', + '.stryker-tmp', + 'artifacts', + 'coverage', + 'dist', + 'node_modules', + 'out', + 'reports' +]) + +const normalize = value => value.split(path.sep).join('/') +const readJson = (root, relativePath) => + JSON.parse(fs.readFileSync(path.join(root, relativePath), 'utf8')) + +export function governedScopePaths(projects, serviceOperations) { + return [ + ...new Set([ + ...projects.projects.map(project => project.path), + ...serviceOperations.services.map(service => service.path) + ]) + ] + .filter(projectPath => projectPath !== '.') + .sort((left, right) => left.localeCompare(right)) +} + +export function renderPackageAgentPointer(projectPath) { + const rootInstructions = normalize(path.posix.relative(projectPath, 'AGENTS.md')) + const rootContributing = normalize(path.posix.relative(projectPath, 'CONTRIBUTING.md')) + return `# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](${rootInstructions}) +and [contribution policy](${rootContributing}). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, \`docs/\`, +\`specs/\`, or the applicable operator guide, and propose shared policy at the +repository root. +` +} + +function walk(root, relativeDirectory = '.') { + const absoluteDirectory = path.join(root, relativeDirectory) + if (!fs.existsSync(absoluteDirectory)) return [] + const entries = [] + for (const entry of fs.readdirSync(absoluteDirectory, { withFileTypes: true })) { + if (SKIPPED_DIRECTORIES.has(entry.name)) continue + const relativePath = normalize(path.join(relativeDirectory, entry.name)).replace(/^\.\//, '') + entries.push({ path: relativePath, name: entry.name, isDirectory: entry.isDirectory() }) + if (entry.isDirectory()) entries.push(...walk(root, relativePath)) + } + return entries +} + +function requireFile(root, relativePath, errors) { + if (!fs.existsSync(path.join(root, relativePath))) { + errors.push(`required contributor-policy file is missing: ${relativePath}`) + return undefined + } + return fs.readFileSync(path.join(root, relativePath), 'utf8') +} + +function validateAuthority(root, policy, errors) { + for (const relativePath of Object.values(policy.authority ?? {})) { + if (relativePath.endsWith('ISSUE_TEMPLATE')) { + if (!fs.statSync(path.join(root, relativePath), { throwIfNoEntry: false })?.isDirectory()) { + errors.push(`required contributor-policy directory is missing: ${relativePath}`) + } + } else { + requireFile(root, relativePath, errors) + } + } + + for (const workflow of policy.requiredRootWorkflows ?? []) { + requireFile(root, workflow, errors) + } + + const pullRequestTemplate = requireFile(root, policy.authority.pullRequestTemplate, errors) + if (pullRequestTemplate !== undefined) { + for (const fragment of policy.requiredPullRequestEvidence ?? []) { + if (!pullRequestTemplate.toLowerCase().includes(fragment.toLowerCase())) { + errors.push( + `${policy.authority.pullRequestTemplate} must require ${JSON.stringify(fragment)}` + ) + } + } + } +} + +function validatePointers(root, scopePaths, entries, errors) { + const expectedAgentPaths = new Set([ + 'AGENTS.md', + ...scopePaths.map(projectPath => `${projectPath}/AGENTS.md`) + ]) + const actualAgentPaths = entries + .filter(entry => !entry.isDirectory && entry.name === 'AGENTS.md') + .map(entry => entry.path) + .sort((left, right) => left.localeCompare(right)) + + for (const projectPath of scopePaths) { + const relativePath = `${projectPath}/AGENTS.md` + const source = requireFile(root, relativePath, errors) + if (source !== undefined && source !== renderPackageAgentPointer(projectPath)) { + errors.push(`${relativePath} must be the generated root-policy pointer`) + } + } + + for (const actualPath of actualAgentPaths) { + if (!expectedAgentPaths.has(actualPath)) { + errors.push(`unregistered nested agent policy is forbidden: ${actualPath}`) + } + } +} + +function validateNoNestedPolicy(root, policy, entries, errors) { + for (const entry of entries) { + if (entry.isDirectory && entry.name === '.github' && entry.path !== '.github') { + errors.push(`nested GitHub configuration is forbidden: ${entry.path}`) + } + if ( + !entry.isDirectory && + policy.prohibitedNestedPolicyFiles.includes(entry.name) && + entry.path !== entry.name + ) { + errors.push(`package-local contributor policy is forbidden: ${entry.path}`) + } + } + + for (const relativePath of policy.retiredPackageContributionFiles ?? []) { + if (fs.existsSync(path.join(root, relativePath))) { + errors.push(`retired package contribution file returned: ${relativePath}`) + } + } +} + +function validateHistoricalDispositions(root, policy, errors) { + const files = [] + for (const disposition of policy.historicalGitHubDispositions ?? []) { + for (const field of ['id', 'decision', 'replacement', 'rationale']) { + if (typeof disposition[field] !== 'string' || disposition[field].trim() === '') { + errors.push(`historical GitHub disposition must define ${field}`) + } + } + if (!Array.isArray(disposition.files) || disposition.files.length === 0) { + errors.push(`${disposition.id ?? ''} must inventory retired files`) + continue + } + for (const relativePath of disposition.files) { + files.push(relativePath) + if (fs.existsSync(path.join(root, relativePath))) { + errors.push(`historical nested GitHub file returned: ${relativePath}`) + } + } + } + const duplicates = files.filter((file, index) => files.indexOf(file) !== index) + for (const duplicate of new Set(duplicates)) { + errors.push(`historical GitHub file has multiple dispositions: ${duplicate}`) + } +} + +function validateLegacyAgentConsolidation(root, policy, scopePaths, errors) { + const consolidation = policy.legacyAgentConsolidation + if (consolidation === undefined) return + + for (const field of ['decision', 'replacement', 'rationale']) { + if (typeof consolidation[field] !== 'string' || consolidation[field].trim() === '') { + errors.push(`legacy agent consolidation must define ${field}`) + } + } + if (!Array.isArray(consolidation.priorFiles) || consolidation.priorFiles.length === 0) { + errors.push('legacy agent consolidation must inventory prior files') + return + } + + const governedAgentPaths = new Set(scopePaths.map(projectPath => `${projectPath}/AGENTS.md`)) + const duplicates = consolidation.priorFiles.filter( + (file, index) => consolidation.priorFiles.indexOf(file) !== index + ) + for (const duplicate of new Set(duplicates)) { + errors.push(`legacy agent file has multiple dispositions: ${duplicate}`) + } + + for (const relativePath of consolidation.priorFiles) { + if (!governedAgentPaths.has(relativePath)) { + errors.push(`legacy agent file is not a governed scope: ${relativePath}`) + continue + } + const projectPath = path.posix.dirname(relativePath) + const source = requireFile(root, relativePath, errors) + if (source !== undefined && source !== renderPackageAgentPointer(projectPath)) { + errors.push(`legacy agent file was not replaced by the generated pointer: ${relativePath}`) + } + } +} + +export function evaluateContributorPolicy({ + root = REPOSITORY_ROOT, + policy = readJson(root, 'governance/contributor-policy.json'), + projects = readJson(root, 'governance/repository-health/projects.json'), + serviceOperations = readJson(root, 'governance/service-operations.json') +} = {}) { + const errors = [] + if (policy.schemaVersion !== 1) errors.push('contributor policy schemaVersion must be 1') + if (typeof policy.owner !== 'string' || policy.owner.trim() === '') { + errors.push('contributor policy must define an owner') + } + const scopePaths = governedScopePaths(projects, serviceOperations) + const entries = walk(root) + validateAuthority(root, policy, errors) + validatePointers(root, scopePaths, entries, errors) + validateNoNestedPolicy(root, policy, entries, errors) + validateHistoricalDispositions(root, policy, errors) + validateLegacyAgentConsolidation(root, policy, scopePaths, errors) + return { + errors, + summary: { + scopedProjectsAndServices: scopePaths.length, + historicalGitHubFiles: (policy.historicalGitHubDispositions ?? []).reduce( + (count, disposition) => count + (disposition.files?.length ?? 0), + 0 + ), + consolidatedLegacyAgentFiles: policy.legacyAgentConsolidation?.priorFiles?.length ?? 0, + retiredPackageContributionFiles: policy.retiredPackageContributionFiles?.length ?? 0 + } + } +} + +export function syncPackageAgentPointers(root = REPOSITORY_ROOT) { + const projects = readJson(root, 'governance/repository-health/projects.json') + const serviceOperations = readJson(root, 'governance/service-operations.json') + const scopePaths = governedScopePaths(projects, serviceOperations) + for (const projectPath of scopePaths) { + const target = path.join(root, projectPath, 'AGENTS.md') + fs.mkdirSync(path.dirname(target), { recursive: true }) + fs.writeFileSync(target, renderPackageAgentPointer(projectPath)) + } + return scopePaths.length +} + +function run() { + const arguments_ = process.argv.slice(2) + if (arguments_.some(argument => argument !== '--write')) { + throw new Error('Usage: node scripts/contributor-policy.mjs [--write]') + } + if (arguments_.includes('--write')) { + const count = syncPackageAgentPointers() + console.log(`Synchronized ${count} package and service AGENTS.md pointers`) + } + const result = evaluateContributorPolicy() + if (result.errors.length > 0) { + console.error(`Contributor policy failed (${result.errors.length} finding(s)):`) + for (const error of result.errors) console.error(`- ${error}`) + process.exitCode = 1 + return + } + console.log( + `Contributor policy passed: ${result.summary.scopedProjectsAndServices} scoped pointers, ` + + `${result.summary.consolidatedLegacyAgentFiles} legacy agent guides consolidated, ` + + `${result.summary.historicalGitHubFiles} historical GitHub files retired, ` + + `${result.summary.retiredPackageContributionFiles} package policies consolidated` + ) +} + +const isMain = + process.argv[1] !== undefined && + import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href +if (isMain) run() diff --git a/scripts/contributor-policy.test.mjs b/scripts/contributor-policy.test.mjs new file mode 100644 index 000000000..6bb76b72d --- /dev/null +++ b/scripts/contributor-policy.test.mjs @@ -0,0 +1,141 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import test from 'node:test' + +import { + REPOSITORY_ROOT, + evaluateContributorPolicy, + governedScopePaths, + renderPackageAgentPointer +} from './contributor-policy.mjs' + +test('current contributor and agent policy is uniform across the governed stack', () => { + const result = evaluateContributorPolicy() + assert.deepEqual(result.errors, []) + assert.equal(result.summary.scopedProjectsAndServices, 43) + assert.equal(result.summary.consolidatedLegacyAgentFiles, 31) + assert.equal(result.summary.historicalGitHubFiles, 49) + assert.equal(result.summary.retiredPackageContributionFiles, 8) +}) + +test('governed scopes combine workspace projects and standalone services exactly once', () => { + assert.deepEqual( + governedScopePaths( + { + projects: [{ path: '.' }, { path: 'packages/example' }, { path: 'infra/shared' }] + }, + { + services: [{ path: 'infra/shared' }, { path: 'infra/service' }] + } + ), + ['infra/service', 'infra/shared', 'packages/example'] + ) +}) + +test('package pointers lead to root policy without defining local conventions', () => { + assert.equal( + renderPackageAgentPointer('packages/wallet/example'), + `# ts-stack agent instructions + +This project follows the repository-wide [agent instructions](../../../AGENTS.md) +and [contribution policy](../../../CONTRIBUTING.md). Read and follow both files +before changing anything in this directory. + +Do not add package-local agent or contribution conventions. Put +package-specific technical information in the package README, \`docs/\`, +\`specs/\`, or the applicable operator guide, and propose shared policy at the +repository root. +` + ) +}) + +test('the repository root remains the executable policy authority', () => { + for (const relativePath of [ + 'AGENTS.md', + 'CONTRIBUTING.md', + 'CODE_OF_CONDUCT.md', + '.github/pull_request_template.md', + 'governance/contributor-policy.json' + ]) { + assert.equal( + fs.existsSync(path.join(REPOSITORY_ROOT, relativePath)), + true, + `${relativePath} must exist` + ) + } +}) + +test('a nested policy file or GitHub directory is rejected', () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ts-stack-contributor-policy-')) + try { + for (const relativePath of [ + 'AGENTS.md', + 'CONTRIBUTING.md', + 'CODE_OF_CONDUCT.md', + '.github/SECURITY.md', + '.github/pull_request_template.md', + '.github/ISSUE_TEMPLATE' + ]) { + const target = path.join(root, relativePath) + if (relativePath.endsWith('ISSUE_TEMPLATE')) { + fs.mkdirSync(target, { recursive: true }) + } else { + fs.mkdirSync(path.dirname(target), { recursive: true }) + fs.writeFileSync( + target, + 'exact-head zero new Sonar findings CodeQL self-review Documentation, changelog, migration Review conversations are resolved all applicable checks' + ) + } + } + fs.mkdirSync(path.join(root, 'packages/example/.github'), { recursive: true }) + fs.writeFileSync( + path.join(root, 'packages/example/AGENTS.md'), + renderPackageAgentPointer('packages/example') + ) + fs.writeFileSync(path.join(root, 'packages/example/CONTRIBUTING.md'), 'local rules') + + const result = evaluateContributorPolicy({ + root, + policy: { + schemaVersion: 1, + owner: 'maintainers', + authority: { + agentInstructions: 'AGENTS.md', + contributionGuide: 'CONTRIBUTING.md', + codeOfConduct: 'CODE_OF_CONDUCT.md', + securityPolicy: '.github/SECURITY.md', + pullRequestTemplate: '.github/pull_request_template.md', + issueTemplateDirectory: '.github/ISSUE_TEMPLATE' + }, + prohibitedNestedPolicyFiles: [ + 'CONTRIBUTING.md', + 'CODE_OF_CONDUCT.md', + 'CLAUDE.md', + 'GEMINI.md', + '.cursorrules' + ], + requiredRootWorkflows: [], + requiredPullRequestEvidence: [ + 'exact-head', + 'zero new Sonar findings', + 'CodeQL', + 'self-review', + 'Documentation, changelog, migration', + 'Review conversations are resolved', + 'all applicable checks' + ], + historicalGitHubDispositions: [], + retiredPackageContributionFiles: [] + }, + projects: { projects: [{ path: '.' }, { path: 'packages/example' }] }, + serviceOperations: { services: [] } + }) + + assert.match(result.errors.join('\n'), /nested GitHub configuration is forbidden/) + assert.match(result.errors.join('\n'), /package-local contributor policy is forbidden/) + } finally { + fs.rmSync(root, { recursive: true, force: true }) + } +}) diff --git a/specs/reliability/README.md b/specs/reliability/README.md index 8d6cfa65e..3b00dcbe8 100644 --- a/specs/reliability/README.md +++ b/specs/reliability/README.md @@ -9,9 +9,10 @@ Use [Generated Stack Facts](../../docs/reference/stack-facts.md) for current versions/runtime profiles, [Repository Health](../../docs/reference/repository-health.md) for enforced controls, [Security Policy](../../.github/SECURITY.md) for current security scope, and -[tracker #324](https://github.com/bsv-blockchain/ts-stack/issues/324) for -unfinished work. Keep the files below unchanged except for factual archival -corrections; new findings belong in current governance or the tracker. +[final QA issue #400](https://github.com/bsv-blockchain/ts-stack/issues/400) +for unfinished test/reliability validation. Keep the files below unchanged +except for factual archival corrections; new findings belong in current +governance or a focused issue. ## Registry From fcda716e370b0e3ab4b870ec2be93d5a2b38a771 Mon Sep 17 00:00:00 2001 From: Ty J Everett Date: Thu, 30 Jul 2026 11:02:14 -0700 Subject: [PATCH 2/2] chore(ci): refresh corrected PR evidence