@bsv/dpp-overlay-topics 0.4.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +169 -0
  3. package/dist/anchorStorage.d.ts +58 -0
  4. package/dist/anchorStorage.d.ts.map +1 -0
  5. package/dist/anchorStorage.js +72 -0
  6. package/dist/anchorStorage.js.map +1 -0
  7. package/dist/attestationAnchor.d.ts +10 -0
  8. package/dist/attestationAnchor.d.ts.map +1 -0
  9. package/dist/attestationAnchor.js +10 -0
  10. package/dist/attestationAnchor.js.map +1 -0
  11. package/dist/attestationStorage.d.ts +49 -0
  12. package/dist/attestationStorage.d.ts.map +1 -0
  13. package/dist/attestationStorage.js +78 -0
  14. package/dist/attestationStorage.js.map +1 -0
  15. package/dist/capabilities.d.ts +127 -0
  16. package/dist/capabilities.d.ts.map +1 -0
  17. package/dist/capabilities.js +210 -0
  18. package/dist/capabilities.js.map +1 -0
  19. package/dist/engineStorage.d.ts +106 -0
  20. package/dist/engineStorage.d.ts.map +1 -0
  21. package/dist/engineStorage.js +253 -0
  22. package/dist/engineStorage.js.map +1 -0
  23. package/dist/evidenceExport.d.ts +189 -0
  24. package/dist/evidenceExport.d.ts.map +1 -0
  25. package/dist/evidenceExport.js +517 -0
  26. package/dist/evidenceExport.js.map +1 -0
  27. package/dist/history.d.ts +114 -0
  28. package/dist/history.d.ts.map +1 -0
  29. package/dist/history.js +215 -0
  30. package/dist/history.js.map +1 -0
  31. package/dist/index.d.ts +269 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +1223 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/lib.d.ts +31 -0
  36. package/dist/lib.d.ts.map +1 -0
  37. package/dist/lib.js +31 -0
  38. package/dist/lib.js.map +1 -0
  39. package/dist/limits.d.ts +44 -0
  40. package/dist/limits.d.ts.map +1 -0
  41. package/dist/limits.js +44 -0
  42. package/dist/limits.js.map +1 -0
  43. package/dist/lsAttestation.d.ts +21 -0
  44. package/dist/lsAttestation.d.ts.map +1 -0
  45. package/dist/lsAttestation.js +45 -0
  46. package/dist/lsAttestation.js.map +1 -0
  47. package/dist/lsDpp.d.ts +53 -0
  48. package/dist/lsDpp.d.ts.map +1 -0
  49. package/dist/lsDpp.js +148 -0
  50. package/dist/lsDpp.js.map +1 -0
  51. package/dist/lsUoraDpp.d.ts +55 -0
  52. package/dist/lsUoraDpp.d.ts.map +1 -0
  53. package/dist/lsUoraDpp.js +152 -0
  54. package/dist/lsUoraDpp.js.map +1 -0
  55. package/dist/policyConfig.d.ts +31 -0
  56. package/dist/policyConfig.d.ts.map +1 -0
  57. package/dist/policyConfig.js +112 -0
  58. package/dist/policyConfig.js.map +1 -0
  59. package/dist/retraction.d.ts +41 -0
  60. package/dist/retraction.d.ts.map +1 -0
  61. package/dist/retraction.js +147 -0
  62. package/dist/retraction.js.map +1 -0
  63. package/dist/storage.d.ts +117 -0
  64. package/dist/storage.d.ts.map +1 -0
  65. package/dist/storage.js +158 -0
  66. package/dist/storage.js.map +1 -0
  67. package/dist/sync.d.ts +75 -0
  68. package/dist/sync.d.ts.map +1 -0
  69. package/dist/sync.js +118 -0
  70. package/dist/sync.js.map +1 -0
  71. package/dist/tmAttestation.d.ts +36 -0
  72. package/dist/tmAttestation.d.ts.map +1 -0
  73. package/dist/tmAttestation.js +50 -0
  74. package/dist/tmAttestation.js.map +1 -0
  75. package/dist/tmDpp.d.ts +154 -0
  76. package/dist/tmDpp.d.ts.map +1 -0
  77. package/dist/tmDpp.js +397 -0
  78. package/dist/tmDpp.js.map +1 -0
  79. package/dist/tmUoraDpp.d.ts +73 -0
  80. package/dist/tmUoraDpp.d.ts.map +1 -0
  81. package/dist/tmUoraDpp.js +150 -0
  82. package/dist/tmUoraDpp.js.map +1 -0
  83. package/dist/uoraAnchor.d.ts +209 -0
  84. package/dist/uoraAnchor.d.ts.map +1 -0
  85. package/dist/uoraAnchor.js +433 -0
  86. package/dist/uoraAnchor.js.map +1 -0
  87. package/package.json +51 -0
package/LICENSE ADDED
@@ -0,0 +1,58 @@
1
+ Open BSV License Version 6 – granted by BSV Association, Alpenstrasse 15, 6300
2
+ Zug, Switzerland (CHE-427.008.338) ("Licensor"), to you as a user (henceforth
3
+ "You", "User" or "Licensee").
4
+
5
+ For the purposes of this license, the definitions below have the following
6
+ meanings:
7
+
8
+ "Bitcoin Protocol" means the protocol implementation, cryptographic rules,
9
+ network protocols, and consensus mechanisms in the Bitcoin White Paper as
10
+ described here https://protocol.bsvblockchain.org.
11
+
12
+ "Bitcoin White Paper" means the paper entitled 'Bitcoin: A Peer-to-Peer
13
+ Electronic Cash System' published by 'Satoshi Nakamoto' in October 2008.
14
+
15
+ "BSV Blockchain" means:
16
+
17
+ (a) the Bitcoin blockchain containing block height #556767 with the hash
18
+ "000000000000000001d956714215d96ffc00e0afda4cd0a96c96f8d802b1662b" and
19
+ that contains the longest honest persistent chain of blocks which has been
20
+ produced in a manner which is consistent with the rules set forth in the
21
+ Network Access Rules; and
22
+ (b) the test blockchains that contain the longest honest persistent chains of
23
+ blocks which has been produced in a manner which is consistent with the
24
+ rules set forth in the Network Access Rules.
25
+
26
+ "Network Access Rules" or "Rules" means the set of rules regulating the
27
+ relationship between BSV Association and the nodes on BSV based on the Bitcoin
28
+ Protocol rules and those set out in the Bitcoin White Paper, and available here
29
+ https://bsvblockchain.org/network-access-rules.
30
+
31
+ "Software" means the software the subject of this license, including any/all
32
+ intellectual property rights therein and associated documentation files.
33
+
34
+ BSV Association grants permission, free of charge and on a non-exclusive basis
35
+ to any person obtaining a copy of the Software to deal in the Software, including
36
+ without limitation the rights to use, copy, modify, merge, publish, distribute,
37
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the
38
+ Software is furnished to do so, subject to and conditioned upon the following
39
+ conditions:
40
+
41
+ 1 - The text "© BSV Association", and this license shall be included in all
42
+ copies or substantial portions of the Software.
43
+
44
+ 2 - The Software, and any software that is derived from the Software or parts
45
+ thereof, may only be used exclusively on the BSV Blockchain.
46
+
47
+ For the avoidance of doubt, this license is granted subject to and conditioned
48
+ upon your compliance with these terms only and is limited to uses on the BSV
49
+ Blockchain. Any exercise of rights not compliant with these terms including
50
+ use not for the BSV Blockchain is deemed outside the scope of the license.
51
+
52
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
53
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES REGARDING ENTITLEMENT,
54
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
55
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS THEREOF BE LIABLE FOR ANY CLAIM,
56
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
57
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
58
+ DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # @bsv/dpp-overlay-topics
2
+
3
+ **Experimental prerelease:** For implementation and interoperability testing. APIs may change significantly before a stable release. Pin exact package versions and retain your lockfile. This package is not declared production-ready. Package versions are separate from the specification, wire-format and frozen profile versions they implement.
4
+
5
+ ## Install
6
+
7
+ This is a pre-1.0 candidate. After publication, install the selected version from npm:
8
+
9
+ ```sh
10
+ npm install --save-exact @bsv/dpp-overlay-topics@0.4.0-beta.1
11
+ ```
12
+
13
+ This is a Node >=22 library for overlay operators and server integrations. Importing it does not start an HTTP service. Applications using a remote overlay do not need this package in their browser bundle. No repository checkout or package build is needed after installation.
14
+
15
+ The package serves native passport history (`tm_dpp`/`ls_dpp`), current complete-representation anchors (`tm_attestation`/`ls_attestation`) and historical UORA-named anchors (`tm_uora_dpp`/`ls_uora_dpp`). Native token admission checks `@bsv/dpp-core` rules. Anchor admission checks its own exact script and service signature; credential proof, authority and status require separate evidence.
16
+
17
+ The package is two things at once, and which one you get depends on how you
18
+ enter it.
19
+
20
+ **As a library.** `main` and the `exports` map point at `dist/lib.js`, so
21
+ `import ... from '@bsv/dpp-overlay-topics'` gets the topic manager, the lookup
22
+ service and the record stores, and nothing that listens on a port. This is how
23
+ the demonstration app uses it: both components run in process against
24
+ `InMemoryDppStorage`, which is what its offline mode and the tests are.
25
+
26
+ **As a service.** `src/index.ts` is an HTTP host for the same topic and lookup components, speaking the ecosystem's standard wire (BRC-22 `POST /submit`, BRC-24 `POST /lookup`, `POST /arc-ingest` for merkle proofs, plus `GET /health`), which is exactly the contract `contracts/overlay.yaml` in this repository pins, together with the five extension routes that contract documents beside them: `GET /capabilities`, `GET /history`, `GET /evidence-package`, `GET /evidence-export` and `POST /retract` (see [The extension routes](#the-extension-routes)), and the two GASP routes a synchronising peer reads. It is reached by path and never by specifier: `npm start` runs `node dist/index.js` and the Dockerfile's `CMD` names the same file. That is deliberate, so importing the package can never start a server.
27
+
28
+ The service boots only when node runs the file directly, which is how
29
+ `test/http.test.ts` drives `createRequestHandler` and `startOverlayService`
30
+ without a container.
31
+
32
+ **Proofs.** The host does not broadcast, so no broadcaster's callback reaches it unasked. A writer pushes each state's merkle path to `POST /arc-ingest` once its wallet has it, or points its broadcaster's callback URL here; the proof is checked to contain the transaction and validated against the header source before the stored BEEF is updated, and the lookup then serves the state proven (`spec/services.md` §2, `spec/writing.md` §7). Re-announcing a mined state does nothing: the engine skips a txid it already holds.
33
+
34
+ ## Running it
35
+
36
+ ```
37
+ npm run build # tsc to dist/, needed before start and before the app imports it
38
+ npm run typecheck # the same compile, emitting nothing
39
+ npm test # admission policy, lookup indexing, engine wiring, the HTTP surface
40
+ npm start # node dist/index.js
41
+ ```
42
+
43
+ Configuration is environment only, and an unset variable switches its feature off or falls back; only a missing identity key, or a publisher policy file that does not verify, stops the boot. The image builds from the repository root, not from this directory, because the service depends on the `@bsv/dpp-core` workspace:
44
+
45
+ ```
46
+ docker build -f packages/overlay-topics/Dockerfile -t dpp-overlay .
47
+ ```
48
+
49
+ ## Configuration
50
+
51
+ Everything is environment. An unset variable switches its feature off or falls back; the variables that can fail the boot are the identity key, because admitting without one would admit anything, and the publisher policy file, because admitting under a chain that does not verify would admit under whatever a file said.
52
+
53
+ | Variable | Effect when set | When unset |
54
+ |----------|-----------------|------------|
55
+ | `PORT` | Port to bind. Platforms inject it. | `8080` |
56
+ | `SERVICE_IDENTITY_KEY` | The public key `server_signature` is verified against (`spec/record-model.md` §5). 66 hex characters, compressed. Under `PUBLISHER_POLICY_FILE` it is optional and consulted only for a topic the policy's `scope.topics` leaves out. | Falls back to deriving it from `SERVER_PRIVATE_KEY`, with a warning; if neither is set and no policy file is, the boot fails |
57
+ | `SERVER_PRIVATE_KEY` | Fallback source for the above. Set the public key instead: the service only ever needs the public half. | See above |
58
+ | `MONGO_URL` | Persist the engine's UTXO state and the passport and both anchor indexes. | In memory, with a warning. Restart loses the index |
59
+ | `MONGO_DB` | Database name. | The connection string's default |
60
+ | `NETWORK` | `main` or `test`. | `main` |
61
+ | `WOC_API_KEY` | WhatsOnChain API key for header lookups. Raises the rate limit. | Anonymous access |
62
+ | `CHAIN_TRACKER` | `scripts-only` disables SPV verification of submissions. Local development only; hosted it would admit unproved ancestry. | WhatsOnChain on `NETWORK` |
63
+ | `SUBMIT_TOKEN` | Shared secret required as `Authorization: Bearer` on `POST /submit` and `POST /retract`, the two routes that change what the index holds. `/lookup`, `/history`, `/capabilities`, `/evidence-package` and `/health` stay open; `/evidence-export` has its own bearer, `EXPORT_TOKEN`. | `/submit` and `/retract` are open to anyone, which is only acceptable on a local container. Set it on any reachable deployment |
64
+ | `ARC_CALLBACK_TOKEN` | Shared secret required on `POST /arc-ingest`, as `Authorization: Bearer` or `X-Callback-Token`, the two ways an ARC-compatible broadcaster sends the token it was given at submission. | `/arc-ingest` is open to anyone. Every proof is still verified against block headers before it is stored, so the open route costs header quota, not truth; set it on any reachable deployment |
65
+ | `ANCHOR_SERVICE_KEYS` | Comma-separated identity keys of the anchoring services this instance carries anchors for. Public keys only. A preference, not a security control: every admitted anchor names its own author either way. Under `PUBLISHER_POLICY_FILE`, `tm_attestation` admits from the policy's anchor-publisher keys instead and this list applies to the historical `tm_uora_dpp` rail and to a topic the policy leaves out. | Anchors from any treasury are carried, each still saying whose it is |
66
+ | `OWNER_CONSENT` | `required`: `tm_dpp` also refuses a `TRANSFER` whose actor is neither the previous `owner_identity_key`, nor linked to it by `owner_linkage` in `event_data`, nor a transfer authority. This is the owner-signed transfer of `spec/custody.md` §4, a profile's choice; the topic documentation says it is on. Any other value fails the boot. | Off: any signed `TRANSFER` that spends the tip is admitted, the record model's baseline |
67
+ | `TRANSFER_AUTHORITIES` | Comma-separated identity keys permitted to `TRANSFER` without proving consent (recovery), named in the topic documentation. Public keys only, validated at boot; meaningful with `OWNER_CONSENT`, warned about without it. | None: every `TRANSFER` must prove consent when `OWNER_CONSENT` is set |
68
+ | `ACCEPTANCE_COMMITMENT` | `required`: `tm_dpp` refuses a version 2 `TRANSFER` whose `authorisation_commitment` is empty. This is the managed-custody profile of `spec/managed-custody.md`, declared in the capability document as the custody profile `managed-custody@1`; the topic documentation says it is on. Any other value fails the boot. | Off: a version 2 `TRANSFER` is admitted with or without a commitment, the record model's baseline, declared as `record-model-baseline@2` |
69
+ | `CONTROL_AUTHORITIES` | Comma-separated identity keys whose version 2 `UPDATE`, `TRANSFER` or `RETIRE` is admitted without a control proof (`spec/record-model-v2.md` §6, recovery). Public keys only, validated at boot; named in the topic documentation and the capability document. | `TRANSFER_AUTHORITIES` serves both versions: the same keys that may `TRANSFER` a version 1 passport without consent may act on a version 2 one without a proof |
70
+ | `PUBLIC_URL` | Passed to the engine as its hosting URL. Only meaningful with peer discovery, which is off. | unset |
71
+ | `PUBLISHER_POLICY_FILE` | Path to a JSON file holding the chain of `dpp-publisher-policy@1` documents, oldest first (`spec/services.md` §1, `contracts/publisher-policy.schema.json`). Verified at boot with `verifyPolicyChain`; a chain that does not verify stops the boot, naming the version and the reason. `tm_dpp` then accepts a state's countersignature only from a state-publisher key active at the state's own timestamp, so a retired key still admits the states it signed while active and a key admits nothing timestamped before its activation; `tm_attestation` accepts anchors only from anchor-publisher keys active at admission time, because an anchor carries no timestamp (announce a backlog before retiring its key). `GET /capabilities` names the keys active now and the version in force. | The single `SERVICE_IDENTITY_KEY` is an implicit single-operator policy with no rotation history, exactly as before, and the capability document says so |
72
+ | `OPERATOR_IDENTITY_KEYS` | Comma-separated `operator=compressedKey` pairs naming each operator's identity key, which the chain's genesis is signed by; the policy itself is never the source of the key that authorises it. Required with `PUBLISHER_POLICY_FILE`; malformed entries stop the boot. | Ignored with a warning when set alone |
73
+ | `EXPORT_SIGNING_KEY` | Private key, hex, that signs `dpp-evidence-package@1` manifests served by `GET /evidence-package` and by every part of `GET /evidence-export` (`spec/portable-evidence.md` §2). A key of its own, never the publisher key: it says which node assembled a package, nothing about admission. Malformed, it stops the boot. | Both routes answer 503 `export-unavailable` and the capability document lists the export as unsupported |
74
+ | `EXPORT_TOKEN` | Shared secret required as `Authorization: Bearer` on `GET /evidence-export`, the complete export, whose parts are each bounded but whose number is not. The bounded `GET /evidence-package` and `GET /history` stay open either way; the capability document reports `limits.evidenceExport` as `bearer`. | `GET /evidence-export` is open, as `GET /history` is, and the capability document reports `open`; set the token on any reachable deployment that serves long lineages |
75
+ | `SYNC_PEERS` | Comma-separated base URLs of the operators this node synchronises `tm_dpp` and `tm_attestation` from, through the overlay SDK's GASP over `POST /requestSyncResponse` and `POST /requestForeignGASPNode` (see [Running two operators locally](#running-two-operators-locally)). Static discovery: the peers are the ones named, never ones a lookup found, and a peer makes evidence findable and never admitted, because every offered output passes the same topic managers as `/submit`. Anything that is not an http or https URL stops the boot. | No synchronisation, exactly as before; the capability document says `discovery: none`, `gasp: false` |
76
+ | `SYNC_INTERVAL_MS` | Milliseconds between synchronisation rounds after the first, which runs once the socket is listening. `0` runs the startup round and no other. A failing peer is a log line and never stops the node or blocks a request. | `60000` |
77
+ | `SYNC_LEGACY` | `1` synchronises the historical `tm_uora_dpp` topic from the same peers as well. | Only the two current topics synchronise |
78
+
79
+ ## The extension routes
80
+
81
+ Five routes the reference deployment serves beside the ecosystem's wire, each in the shape a contract under `contracts/` fixes and each documented in `contracts/overlay.yaml`, followed by the two GASP routes. None replaces the bounded lookup, which stays exactly what it is.
82
+
83
+ | Route | What it serves | Access |
84
+ |---|---|---|
85
+ | `GET /capabilities` | The capability document | Open |
86
+ | `GET /history` | Pages of one passport's history over a stable snapshot | Open |
87
+ | `GET /evidence-package` | The bounded signed package of the newest 500 states | Open; 503 without `EXPORT_SIGNING_KEY` |
88
+ | `GET /evidence-export` | The complete export as signed, resumable parts over one snapshot | Bearer `EXPORT_TOKEN` when set; 503 without `EXPORT_SIGNING_KEY` |
89
+ | `POST /retract` | Withdrawal of an admitted output the network refused | Bearer `SUBMIT_TOKEN` |
90
+ | `POST /requestSyncResponse`, `POST /requestForeignGASPNode` | The overlay protocol's synchronisation routes a peer reads | Open, bounded |
91
+
92
+ ### `GET /capabilities`
93
+
94
+ The capability document of `spec/conformance.md` §4, in the shape of `contracts/capabilities.schema.json`, built from the constants the wire is built from and from the configuration the node runs with, never typed by hand: roles, protocol versions, the operator profile, representations, proof suites, anchor formats (current, historical and refused), topics and services, the publisher key policy in force (the keys active at the moment of the request, the owner-signed transfer and its transfer authorities), the synchronisation profile, the limits, and by name what the node does not do. A claim of support, checkable against the ledger; never proof of authority or of conformance. `conformance/examples/capabilities-reference-node.json` is what the reference configuration produces.
95
+
96
+ ### `GET /history?passportId=|uid=&limit=&cursor=`
97
+
98
+ Pages of a passport's complete history over a stable snapshot (`spec/portable-evidence.md` §1, `contracts/paginated-history.schema.json`). The first page pins a snapshot at the highest record sequence the index had assigned; later pages read only records at or below it, in the index's own insertion order, so a state admitted mid-export appears in no page and shifts no boundary, and two readers of one snapshot receive the same pages. Each item carries the record's `txid`, `outputIndex`, `op`, `timestamp`, `previousTxid`, `spent`, `spendingTxid` and `sequence`; the bytes come from `POST /lookup` or `GET /evidence-package`. `scope.sequenceRange` is the span each page covers, so consecutive pages tile the snapshot and a reader detects a repeated or missing page from the ranges alone; `completeForSnapshot` is true only on the last page. `limit` defaults to 100 and is at most 500. Cursors are signed under a secret drawn at startup: a tampered, foreign or re-targeted cursor answers 400 `cursor-invalid`, and a snapshot older than ten minutes answers 410 `snapshot-expired` with the instruction to restart without a cursor. The secret is per process, so several replicas of one index behind one address must pin a client to one replica for the life of an export, or share the secret; otherwise a cursor minted by one replica is `cursor-invalid` on another. The record store numbers rows as they are inserted; a MongoDB store that predates the field numbers its rows at boot in `createdAt`, `txid`, `outputIndex` order before the first page is served.
99
+
100
+ ### `GET /evidence-package?passportId=`
101
+
102
+ The `dpp-evidence-package@1` for one passport (`spec/portable-evidence.md` §2, `contracts/evidence-package.schema.json`): every retained state's raw transaction under `transactions/`, the BEEF it is held in under `proofs/` (with the merkle path once `/arc-ingest` delivered it), the publisher policy chain under `authority/` when one is configured, the index's spend observations under `status/` and a `spec/verification.md` report under `reports/`, inventoried by path, media type, length and SHA-256 in a manifest signed with `EXPORT_SIGNING_KEY`. The archive form is this build's own: a JSON envelope `{ manifest, files: { "<path>": "<base64>" } }`, which is exactly the map `inspectEvidencePackage` in `@bsv/dpp-core` takes. The disclosure scope is public and the package is never a recovery backup: the restricted tiers are withheld by name, and a state whose transaction or proof could not be produced is declared absent. A package carries at most 500 states, the newest by the index's sequence, as the bounded lookup keeps the newest; older states are declared absent by outpoint and the snapshot declaration says the package is not complete for its snapshot. The cap is in the capability document as `limits.maxEvidencePackageStates`, and it is what keeps an unauthenticated GET from parsing and signing an unbounded number of transactions. It applies after every record of the passport has been read from the record store, so the database read is proportional to the passport's length even though the BEEF parsing and the signing are capped; the rows are small and indexed by passport, and the read is what `GET /history` does page by page. A second operator restores a passport by submitting the `proofs/` BEEFs, genesis first, to its own `/submit`; `test/evidenceExport.test.ts` does exactly that and then discards the first operator. Without `EXPORT_SIGNING_KEY` the route answers 503 `export-unavailable`. A package cut at the cap names no genesis, because it does not hold one.
103
+
104
+ The complete export is `GET /evidence-export?passportId=...` (`contracts/evidence-export.schema.json`): the first request pins a snapshot and answers the first part, a package over the oldest states within it, holding at most `limits.maxEvidenceExportPartStates` states and closing earlier once their raw transactions and BEEFs exceed `limits.maxEvidenceExportPartBytes`, always with at least one; each part carries a coverage record (passport, snapshot, index, sequence range, number of states, final flag and the SHA-256 of its package manifest as signed) signed with `EXPORT_SIGNING_KEY` under the manifest's own preimage rule, carries that part's spend observations and the policy chain, and carries no report, because the report is the reader's to produce over the joined history; `nextCursor` stays outside the signed record and resumes the next part over the same snapshot under the same cursor rules as `GET /history` (400 `cursor-invalid`, 410 `snapshot-expired`, and a history cursor is refused here by name as an export cursor is there), and the final part ends at the snapshot's sequence with a null cursor. `joinEvidenceExport` in this package is the reader's join: first each part's coverage signature under the exporter's key and the digest binding of its package (`inspectEvidenceExportPart`), so no unsigned field establishes coverage or finality; then the same passport and snapshot, indexes with no repeat, ranges that tile with no gap or overlap, every package inspecting clean, no path repeated with different bytes, and the signed final flag on the last part; only then is the join complete, and `test/evidenceExportParts.test.ts` walks 505 connected states through two parts, joins them, restores them into a second operator and exports them again. With `EXPORT_TOKEN` set the route requires the bearer and answers 401 `export-unauthorised` without it.
105
+
106
+ ### `POST /retract`
107
+
108
+ `{ "txid", "outputIndex", "reason" }`, behind the `/submit` bearer. A writer that announced a state before sending it (which `spec/writing.md` allows) may learn that the network refused the transaction; the index then holds a phantom tip that a later state cannot build on and a reader cannot order. Refused with 409 `retraction-refused` when the index holds a merkle path for the output, when the header source's operator knows the transaction, or when the output is spent (a history is never cut in the middle); 503 `chain-tracker-unavailable` when the network could not be asked. Otherwise the output leaves the engine's storage and the record store as an eviction removes it, the predecessor is unspent and the tip again, the transaction's applied mark is cleared so the same transaction is admitted if announced again, and the retraction is logged with its reason. With `CHAIN_TRACKER=scripts-only` the network cannot be asked; the answer carries `networkChecked: false` and a note saying so, and the capability document lists `retraction-network-check` as unsupported. The Engine's own eviction path is not called as a whole: it prunes the consumed lineage recursively, which on a history-retaining topic is the passport back to its genesis, so the retraction performs its first step and its predecessor repair and stops there (`src/retraction.ts` says why).
109
+
110
+ ### `POST /requestSyncResponse` and `POST /requestForeignGASPNode`
111
+
112
+ The two upstream GASP routes a synchronising peer reads, exactly as the overlay protocol defines them: the first lists the unspent outputs this node holds for the topic in `X-BSV-Topic` since the peer's checkpoint (body: the SDK's `GASPInitialRequest`), the second serves one transaction of a graph with its merkle path when this node holds one (body: `graphID`, `txid`, `outputIndex`). Both are open, like `/lookup`, and bounded like every other POST route: the request body at 8 MiB, and the first route's page at 500 outputs, the request's `limit` defaulting to that and clamped to it (`limits.maxSyncPageSize` in the capability document), because the engine's storage applies no cap of its own and an anonymous peer would otherwise receive every unspent output of a topic in one answer. A peer holding more than 500 tips is synchronised 500 per round, continuing from the checkpoint. Nothing listed by them is trusted by being listed: the peer admits what it fetches through its own topic managers.
113
+
114
+ ## Running two operators locally
115
+
116
+ Two nodes on two ports under one administration, sharing one publisher policy file that names both operators. This proves the mechanism, which is what `test/federation.test.ts` does end to end: the routes, the synchronisation, admission during synchronisation, partition recovery, operator loss and policy rotation. It proves nothing about independence. The operator manifest (`packages/dpp-profiles/manifests/operator/federated-operators@1.json`) says so in as many words: two processes under one administration prove only the mechanism, and `federated-operators@1` cannot be claimed by a deployment until two organisations with separate administrations, credentials, databases and infrastructure have run the exercises and observed the same results. The capability document of a node with a two-operator policy and peers configured therefore claims the profile as a claim of support, not of independence.
117
+
118
+ Build once from the repository root and write a policy file whose scope names both operators, with the operators' identity keys at hand (the chain must verify under `verifyPolicyChain`; `test/policy-fixture.ts` builds one for the tests):
119
+
120
+ ```
121
+ npm run build -w @bsv/dpp-core -w @bsv/dpp-overlay-topics
122
+ ```
123
+
124
+ Operator A, in one terminal:
125
+
126
+ ```
127
+ PORT=18081 CHAIN_TRACKER=scripts-only SUBMIT_TOKEN=operator-a-secret \
128
+ PUBLISHER_POLICY_FILE=./publisher-policy.json \
129
+ OPERATOR_IDENTITY_KEYS='did:example:operator-a=02aa...,did:example:operator-b=03bb...' \
130
+ node packages/overlay-topics/dist/index.js
131
+ ```
132
+
133
+ Operator B, in another, synchronising from A every ten seconds:
134
+
135
+ ```
136
+ PORT=18082 CHAIN_TRACKER=scripts-only SUBMIT_TOKEN=operator-b-secret \
137
+ PUBLISHER_POLICY_FILE=./publisher-policy.json \
138
+ OPERATOR_IDENTITY_KEYS='did:example:operator-a=02aa...,did:example:operator-b=03bb...' \
139
+ SYNC_PEERS=http://127.0.0.1:18081 SYNC_INTERVAL_MS=10000 \
140
+ node packages/overlay-topics/dist/index.js
141
+ ```
142
+
143
+ Announce states and anchors to A with its token (`POST http://127.0.0.1:18081/submit`), then ask both for the same passport (`POST /lookup` with `{"service":"ls_dpp","query":{"passportId":"..."}}`) and compare the BEEF bytes; `GET /history` on each shows the same items in the same order, and `GET /capabilities` on B says `synchronisation.profile` `federated-operators@1`, `discovery` `static-peers` and the peer list, while A, with no peers, says `single-operator@1`. `CHAIN_TRACKER=scripts-only` is the local setting; a hosted pair keeps the default header source, under which both nodes verify every offered graph against block headers before admitting it.
144
+
145
+ What synchronises, and what does not, with the SDK as it stands (`@bsv/overlay` 2.3.1, `@bsv/gasp` 1.3.6). The engine stores a proven state as the compact atomic BEEF of that state alone, so this node answers a peer's `POST /requestForeignGASPNode` from its own storage by the transaction the peer names (the engine's own walk from the graph's root would stop at the first unhydrated input of a proven tip), and a fresh peer fetches a proven lineage state by state through the tip's inputs:
146
+
147
+ - A peer offers the unspent outputs of a topic, the tips. The whole lineage behind an unproven tip arrives through the tip's inputs, so a node that knows nothing of a passport receives every retained state, spent ones included, and admits them genesis first through the same topic managers as `/submit`; a second round admits nothing and creates no duplicate.
148
+ - A state on top of a lineage the peer already holds synchronises once it is proven. The SDK's graph builder needs an unproven transaction's parent in its temporary graph, and its input stripping keeps a parent the peer already holds out of it, so an unproven state announced during a partition waits until its proof reaches the source node (`/arc-ingest`); on the next round it arrives with the proof and is admitted against the predecessor the peer holds. `tm_dpp` reads that predecessor from the engine's storage when the offered BEEF does not carry it, and remembers the states it inspected in one synchronisation pass, because the SDK hands it one state at a time.
149
+ - A proof that reaches one operator after another has synchronised the state does not travel through GASP: the SDK offers outpoints and never re-fetches one the peer holds. Proofs reach each operator through its own `/arc-ingest`, which is what the operator manifest means by proof updates being ingested separately from replaying held outputs; a writer or a gateway pushes the proof to every operator it knows, and a reader of either sees `inclusion` pending until it has.
150
+ - An offered state countersigned by a key outside the policy, or timestamped outside its key's window, is refused by the peer's own `tm_dpp` and the refusal is logged; the offering node changes what can be found, never what can be believed.
151
+
152
+ ## A refused spend of the tip
153
+
154
+ The Engine reads a spend of an admitted output that the topic did not retain as that output's consumption, and its stale-output eviction removes the output and, recursively, every predecessor nothing else consumes. Before it does, it marks the spent output in its own storage and tells every lookup service of the spend, whether or not the spending transaction was admitted. On a history-retaining topic that meant one refused announcement spending the tip (a wrong publisher key, a broken invariant) emptied the passport's index entry, which the 2026-08-12 rehearsal met. Two guards now stand where the topic manager and the lookup service can stand:
155
+
156
+ - `tm_dpp` returns the coins the Engine offered as `coinsToRetain` on every refusal, so the Engine keeps the tip and its lineage.
157
+ - `ls_dpp` records a spend only once the admission of the spending transaction follows, which in `Engine.submit` is the same call when it happens at all; a spend nobody confirms names a state this index never held, and the record keeps the tip unspent.
158
+
159
+ After a refused spend, `/lookup`, `/history` and the record store read exactly as before it, and a valid later state is admitted against the tip (`test/refusedSpend.test.ts`). What the Engine still does with the refused transaction: it marks the tip spent in its own storage, so the tip is not offered to synchronising peers until a valid successor is admitted (the successor is then the offered tip and the lineage arrives through it), and it records the refused transaction as applied to the topic, which on its own would make the same bytes announced again a duplicate no-op that the Engine never re-evaluates. The host reads that record for what it is. When a submission reads as a duplicate for a topic and the engine's storage holds no admitted output of the transaction for that topic, the transaction was refused earlier: the host clears the applied record and submits it again for that topic, so the answer is what the topic managers say today. A state refused for a transient reason (its predecessor announced after it, a policy version not yet loaded, a stranger announcing a successor before the writer could announce its predecessor) is admitted once the reason is gone; one refused for good answers `none` again, with the log saying it was announced and refused before; `duplicate` is answered only for a transaction this index admitted. The spent mark on the tip needs the Engine to distinguish a refused spend from an unretained one, which is an upstream change and not this package's to make.
160
+
161
+ ## Current and historical anchors
162
+
163
+ `AttestationTopicManager`, `AttestationLookupService`, `InMemoryAttestationStorage` and `MongoAttestationStorage` implement the current generic format. `buildAttestationAnchor` and `decodeAttestationAnchor` expose its writer and exact reader. The MongoDB collection is `attestationAnchorsV1`; historical indexes remain separate.
164
+
165
+ The current lookup requires an exact issuer, subject, attestationId, digest or anchoredBy selector. Optional attestationType, representation and mediaType narrow it. `limit` is 1 to 500, default 100. Results sort by txid then outputIndex; pass the final returned `{txid, outputIndex}` as `after` until an empty page. This queries a live local index and does not establish global completeness.
166
+
167
+ The current format includes representation and mediaType and commits to the complete signed representation. The historical decoder retains the original `uora-anchor-v3` digest contract. A service name or format prefix is never silently aliased between these contracts. See [the rules](../../spec/rules.md) and [registry contract](../../contracts/registry.yaml) for credential verification and exact-byte retrieval.
168
+
169
+ The HTTP host exposes documentation, submission, lookup, proof ingestion, the extension routes above and the two GASP routes. SHIP/SLAP advertising is not enabled by this host; peer synchronisation is, from the static peers `SYNC_PEERS` names and from nowhere else.
@@ -0,0 +1,58 @@
1
+ import type { Db } from 'mongodb';
2
+ /** One admitted UORA anchor, flattened for the queries `ls_uora_dpp` answers. */
3
+ export interface UoraAnchorRecord {
4
+ txid: string;
5
+ outputIndex: number;
6
+ /** Lower-case hex SHA-256 of the attestation's canonical form. */
7
+ digest: string;
8
+ attestationId: string;
9
+ /** The claiming party, `did:key`. The primary index: this is the DID-keyed lookup. */
10
+ issuer: string;
11
+ /** The same key as hex, stored so a caller holding a chain key need not encode one. */
12
+ issuerKey: string;
13
+ /** The passport id the claim is about. */
14
+ subject: string;
15
+ uoraType: string;
16
+ /** The anchoring treasury the output names, checked against the locking key. */
17
+ anchoredBy: string;
18
+ /** The anchoring service's per-attestation key, kept so attribution is auditable. */
19
+ lockingKey: string;
20
+ createdAt: Date;
21
+ }
22
+ /**
23
+ * What a caller may select on. Every field is an exact match; there is no
24
+ * prefix or regex search, deliberately, because an unanchored pattern over an
25
+ * attacker-supplied index is the cheapest denial of service an overlay offers.
26
+ */
27
+ export interface UoraAnchorQuery {
28
+ issuer?: string;
29
+ issuerKey?: string;
30
+ subject?: string;
31
+ attestationId?: string;
32
+ digest?: string;
33
+ uoraType?: string;
34
+ anchoredBy?: string;
35
+ limit?: number;
36
+ }
37
+ /** Answers stay bounded whatever the caller asks for. */
38
+ export declare const MAX_ANCHOR_RESULTS = 500;
39
+ export interface UoraAnchorStore {
40
+ insert: (record: UoraAnchorRecord) => Promise<void>;
41
+ delete: (txid: string, outputIndex: number) => Promise<void>;
42
+ find: (query: UoraAnchorQuery) => Promise<UoraAnchorRecord[]>;
43
+ }
44
+ export declare class MongoUoraAnchorStorage implements UoraAnchorStore {
45
+ private readonly anchors;
46
+ constructor(db: Db);
47
+ insert(record: UoraAnchorRecord): Promise<void>;
48
+ delete(txid: string, outputIndex: number): Promise<void>;
49
+ find(query: UoraAnchorQuery): Promise<UoraAnchorRecord[]>;
50
+ }
51
+ export declare class InMemoryUoraAnchorStorage implements UoraAnchorStore {
52
+ private readonly anchors;
53
+ private key;
54
+ insert(record: UoraAnchorRecord): Promise<void>;
55
+ delete(txid: string, outputIndex: number): Promise<void>;
56
+ find(query: UoraAnchorQuery): Promise<UoraAnchorRecord[]>;
57
+ }
58
+ //# sourceMappingURL=anchorStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchorStorage.d.ts","sourceRoot":"","sources":["../src/anchorStorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,EAAE,EAAU,MAAM,SAAS,CAAA;AAErD,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAA;IACd,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAA;IACjB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAA;IAClB,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,IAAI,CAAA;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,yDAAyD;AACzD,eAAO,MAAM,kBAAkB,MAAM,CAAA;AAErC,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACnD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5D,IAAI,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAA;CAC9D;AA0BD,qBAAa,sBAAuB,YAAW,eAAe;IAC5D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;gBAE1C,EAAE,EAAE,EAAE;IAUZ,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ/C,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxD,IAAI,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAQhE;AAED,qBAAa,yBAA0B,YAAW,eAAe;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAE9D,OAAO,CAAC,GAAG;IAIL,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxD,IAAI,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAOhE"}
@@ -0,0 +1,72 @@
1
+ /** Answers stay bounded whatever the caller asks for. */
2
+ export const MAX_ANCHOR_RESULTS = 500;
3
+ /** The selectors, minus the paging control, as a plain object. */
4
+ function selectors(query) {
5
+ const pairs = [];
6
+ const fields = [
7
+ 'issuer',
8
+ 'issuerKey',
9
+ 'subject',
10
+ 'attestationId',
11
+ 'digest',
12
+ 'uoraType',
13
+ 'anchoredBy',
14
+ ];
15
+ for (const key of fields) {
16
+ const value = query[key];
17
+ if (typeof value === 'string' && value !== '')
18
+ pairs.push([key, value]);
19
+ }
20
+ return pairs;
21
+ }
22
+ function bounded(limit) {
23
+ if (limit == null || !Number.isFinite(limit) || limit <= 0)
24
+ return MAX_ANCHOR_RESULTS;
25
+ return Math.min(Math.floor(limit), MAX_ANCHOR_RESULTS);
26
+ }
27
+ export class MongoUoraAnchorStorage {
28
+ anchors;
29
+ constructor(db) {
30
+ this.anchors = db.collection('uoraAnchors');
31
+ void this.anchors.createIndex({ issuer: 1, createdAt: 1 });
32
+ void this.anchors.createIndex({ subject: 1, createdAt: 1 });
33
+ void this.anchors.createIndex({ attestationId: 1 });
34
+ void this.anchors.createIndex({ digest: 1 });
35
+ void this.anchors.createIndex({ anchoredBy: 1, createdAt: 1 });
36
+ void this.anchors.createIndex({ txid: 1, outputIndex: 1 }, { unique: true });
37
+ }
38
+ async insert(record) {
39
+ await this.anchors.updateOne({ txid: record.txid, outputIndex: record.outputIndex }, { $set: record }, { upsert: true });
40
+ }
41
+ async delete(txid, outputIndex) {
42
+ await this.anchors.deleteOne({ txid, outputIndex });
43
+ }
44
+ async find(query) {
45
+ const filter = Object.fromEntries(selectors(query));
46
+ return await this.anchors
47
+ .find(filter)
48
+ .sort({ createdAt: 1 })
49
+ .limit(bounded(query.limit))
50
+ .toArray();
51
+ }
52
+ }
53
+ export class InMemoryUoraAnchorStorage {
54
+ anchors = new Map();
55
+ key(txid, outputIndex) {
56
+ return `${txid}.${outputIndex}`;
57
+ }
58
+ async insert(record) {
59
+ this.anchors.set(this.key(record.txid, record.outputIndex), record);
60
+ }
61
+ async delete(txid, outputIndex) {
62
+ this.anchors.delete(this.key(txid, outputIndex));
63
+ }
64
+ async find(query) {
65
+ const pairs = selectors(query);
66
+ return [...this.anchors.values()]
67
+ .filter((record) => pairs.every(([field, value]) => record[field] === value))
68
+ .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
69
+ .slice(0, bounded(query.limit));
70
+ }
71
+ }
72
+ //# sourceMappingURL=anchorStorage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchorStorage.js","sourceRoot":"","sources":["../src/anchorStorage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The generic anchor format moved into `@bsv/dpp-core` (its `anchor.ts`) so a
3
+ * browser reader and `verifyPassportEvidence` can decode anchors without this
4
+ * package's database and engine dependencies. Every name this module exported
5
+ * is re-exported here unchanged, so the topic manager, the lookup service, the
6
+ * fixtures and every consumer importing from `@bsv/dpp-overlay-topics` keep
7
+ * working without an edit.
8
+ */
9
+ export { ATTESTATION_ANCHOR_BASKET, ATTESTATION_ANCHOR_FIELD_COUNT, ATTESTATION_ANCHOR_PREFIX, ATTESTATION_ANCHOR_PROTOCOL, attestationAnchorFields, attestationAnchorSigningPreimage, buildAttestationAnchor, decodeAttestationAnchor, expectedAttestationLockingKey, inspectAttestationAnchor, type AnchorMetadata, type AnchorSigner, type AttestationAnchor, type AttestationAnchorInspection, } from '@bsv/dpp-core';
10
+ //# sourceMappingURL=attestationAnchor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attestationAnchor.d.ts","sourceRoot":"","sources":["../src/attestationAnchor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,yBAAyB,EACzB,8BAA8B,EAC9B,yBAAyB,EACzB,2BAA2B,EAC3B,uBAAuB,EACvB,gCAAgC,EAChC,sBAAsB,EACtB,uBAAuB,EACvB,6BAA6B,EAC7B,wBAAwB,EACxB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,2BAA2B,GACjC,MAAM,eAAe,CAAA"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The generic anchor format moved into `@bsv/dpp-core` (its `anchor.ts`) so a
3
+ * browser reader and `verifyPassportEvidence` can decode anchors without this
4
+ * package's database and engine dependencies. Every name this module exported
5
+ * is re-exported here unchanged, so the topic manager, the lookup service, the
6
+ * fixtures and every consumer importing from `@bsv/dpp-overlay-topics` keep
7
+ * working without an edit.
8
+ */
9
+ export { ATTESTATION_ANCHOR_BASKET, ATTESTATION_ANCHOR_FIELD_COUNT, ATTESTATION_ANCHOR_PREFIX, ATTESTATION_ANCHOR_PROTOCOL, attestationAnchorFields, attestationAnchorSigningPreimage, buildAttestationAnchor, decodeAttestationAnchor, expectedAttestationLockingKey, inspectAttestationAnchor, } from '@bsv/dpp-core';
10
+ //# sourceMappingURL=attestationAnchor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attestationAnchor.js","sourceRoot":"","sources":["../src/attestationAnchor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,yBAAyB,EACzB,8BAA8B,EAC9B,yBAAyB,EACzB,2BAA2B,EAC3B,uBAAuB,EACvB,gCAAgC,EAChC,sBAAsB,EACtB,uBAAuB,EACvB,6BAA6B,EAC7B,wBAAwB,GAKzB,MAAM,eAAe,CAAA"}
@@ -0,0 +1,49 @@
1
+ import type { Db } from 'mongodb';
2
+ import type { AnchorMetadata } from './attestationAnchor.js';
3
+ export interface AttestationRecord extends AnchorMetadata {
4
+ txid: string;
5
+ outputIndex: number;
6
+ lockingKey: string;
7
+ createdAt: Date;
8
+ }
9
+ export interface AttestationCursor {
10
+ txid: string;
11
+ outputIndex: number;
12
+ }
13
+ export interface AttestationQuery {
14
+ issuer?: string;
15
+ subject?: string;
16
+ attestationId?: string;
17
+ digest?: string;
18
+ anchoredBy?: string;
19
+ attestationType?: string;
20
+ representation?: string;
21
+ mediaType?: string;
22
+ limit?: number;
23
+ after?: AttestationCursor;
24
+ }
25
+ export interface AttestationStore {
26
+ insert(record: AttestationRecord): Promise<void>;
27
+ delete(txid: string, outputIndex: number): Promise<void>;
28
+ find(query: AttestationQuery): Promise<AttestationRecord[]>;
29
+ }
30
+ export declare const MAX_ATTESTATION_RESULTS = 500;
31
+ export declare const DEFAULT_ATTESTATION_RESULTS = 100;
32
+ export declare const ATTESTATION_SELECTORS: readonly ["issuer", "subject", "attestationId", "digest", "anchoredBy", "attestationType", "representation", "mediaType"];
33
+ export declare function validateAttestationQuery(value: unknown): AttestationQuery;
34
+ export declare class InMemoryAttestationStorage implements AttestationStore {
35
+ private readonly records;
36
+ insert(record: AttestationRecord): Promise<void>;
37
+ delete(txid: string, outputIndex: number): Promise<void>;
38
+ find(value: AttestationQuery): Promise<AttestationRecord[]>;
39
+ }
40
+ export declare class MongoAttestationStorage implements AttestationStore {
41
+ private readonly records;
42
+ private ready?;
43
+ constructor(db: Db);
44
+ private ensureIndexes;
45
+ insert(record: AttestationRecord): Promise<void>;
46
+ delete(txid: string, outputIndex: number): Promise<void>;
47
+ find(value: AttestationQuery): Promise<AttestationRecord[]>;
48
+ }
49
+ //# sourceMappingURL=attestationStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attestationStorage.d.ts","sourceRoot":"","sources":["../src/attestationStorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,EAAE,EAAU,MAAM,SAAS,CAAA;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAE5D,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,IAAI,CAAA;CAChB;AACD,MAAM,WAAW,iBAAiB;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,iBAAiB,CAAA;CAC1B;AACD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,IAAI,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAA;CAC5D;AACD,eAAO,MAAM,uBAAuB,MAAM,CAAA;AAC1C,eAAO,MAAM,2BAA2B,MAAM,CAAA;AAC9C,eAAO,MAAM,qBAAqB,2HAA4H,CAAA;AAE9J,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAezE;AAUD,qBAAa,0BAA2B,YAAW,gBAAgB;IACjE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAEzD,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACxD,IAAI,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;CAOlE;AAED,qBAAa,uBAAwB,YAAW,gBAAgB;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,KAAK,CAAC,CAAe;gBACjB,EAAE,EAAE,EAAE;YAEJ,aAAa;IAOrB,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxD,IAAI,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;CAOlE"}
@@ -0,0 +1,78 @@
1
+ export const MAX_ATTESTATION_RESULTS = 500;
2
+ export const DEFAULT_ATTESTATION_RESULTS = 100;
3
+ export const ATTESTATION_SELECTORS = ['issuer', 'subject', 'attestationId', 'digest', 'anchoredBy', 'attestationType', 'representation', 'mediaType'];
4
+ export function validateAttestationQuery(value) {
5
+ if (!value || typeof value !== 'object' || Array.isArray(value))
6
+ throw new Error('query must be an object');
7
+ const query = value;
8
+ for (const [field, entry] of Object.entries(query)) {
9
+ if (ATTESTATION_SELECTORS.includes(field)) {
10
+ if (typeof entry !== 'string' || entry.length === 0 || Buffer.byteLength(entry) > 512 || /[\u0000-\u001f\u007f-\u009f]/u.test(entry))
11
+ throw new Error(`invalid query selector ${field}`);
12
+ }
13
+ else if (field !== 'limit' && field !== 'after')
14
+ throw new Error(`unsupported query field ${field}`);
15
+ }
16
+ if (!ATTESTATION_SELECTORS.slice(0, 5).some(field => query[field] !== undefined))
17
+ throw new Error('query requires issuer, subject, attestationId, digest or anchoredBy');
18
+ if (query.limit !== undefined && (!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > MAX_ATTESTATION_RESULTS))
19
+ throw new Error('limit must be an integer from 1 to 500');
20
+ if (query.after !== undefined) {
21
+ const after = query.after;
22
+ if (!after || typeof after !== 'object' || Array.isArray(after) || Object.keys(after).sort().join(',') !== 'outputIndex,txid' || typeof after.txid !== 'string' || !/^[0-9a-f]{64}$/.test(after.txid) || !Number.isSafeInteger(after.outputIndex) || after.outputIndex < 0)
23
+ throw new Error('after must be an exact transaction outpoint');
24
+ }
25
+ return value;
26
+ }
27
+ function selectors(query) {
28
+ return Object.fromEntries(ATTESTATION_SELECTORS.filter(field => query[field] !== undefined).map(field => [field, query[field]]));
29
+ }
30
+ function compare(a, b) {
31
+ return a.txid < b.txid ? -1 : a.txid > b.txid ? 1 : a.outputIndex - b.outputIndex;
32
+ }
33
+ export class InMemoryAttestationStorage {
34
+ records = new Map();
35
+ async insert(record) {
36
+ const key = `${record.txid}.${record.outputIndex}`;
37
+ if (!this.records.has(key))
38
+ this.records.set(key, { ...record });
39
+ }
40
+ async delete(txid, outputIndex) { this.records.delete(`${txid}.${outputIndex}`); }
41
+ async find(value) {
42
+ const query = validateAttestationQuery(value);
43
+ const filter = Object.entries(selectors(query));
44
+ return [...this.records.values()]
45
+ .filter(record => filter.every(([key, val]) => record[key] === val) && (!query.after || compare(record, query.after) > 0))
46
+ .sort(compare).slice(0, query.limit ?? DEFAULT_ATTESTATION_RESULTS).map(record => ({ ...record }));
47
+ }
48
+ }
49
+ export class MongoAttestationStorage {
50
+ records;
51
+ ready;
52
+ constructor(db) { this.records = db.collection('attestationAnchorsV1'); }
53
+ async ensureIndexes() {
54
+ this.ready ??= (async () => {
55
+ await this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true });
56
+ for (const field of ATTESTATION_SELECTORS.slice(0, 5))
57
+ await this.records.createIndex({ [field]: 1, txid: 1, outputIndex: 1 });
58
+ })().catch(error => { this.ready = undefined; throw error; });
59
+ await this.ready;
60
+ }
61
+ async insert(record) {
62
+ await this.ensureIndexes();
63
+ await this.records.updateOne({ txid: record.txid, outputIndex: record.outputIndex }, { $setOnInsert: record }, { upsert: true });
64
+ }
65
+ async delete(txid, outputIndex) {
66
+ await this.ensureIndexes();
67
+ await this.records.deleteOne({ txid, outputIndex });
68
+ }
69
+ async find(value) {
70
+ const query = validateAttestationQuery(value);
71
+ await this.ensureIndexes();
72
+ const filter = selectors(query);
73
+ if (query.after)
74
+ filter.$or = [{ txid: { $gt: query.after.txid } }, { txid: query.after.txid, outputIndex: { $gt: query.after.outputIndex } }];
75
+ return await this.records.find(filter).sort({ txid: 1, outputIndex: 1 }).limit(query.limit ?? DEFAULT_ATTESTATION_RESULTS).toArray();
76
+ }
77
+ }
78
+ //# sourceMappingURL=attestationStorage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attestationStorage.js","sourceRoot":"","sources":["../src/attestationStorage.ts"],"names":[],"mappings":"AA2BA,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAA;AAC1C,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAA;AAC9C,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,CAAU,CAAA;AAE9J,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAA;IAC3G,MAAM,KAAK,GAAG,KAAgC,CAAA;IAC9C,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACnD,IAAI,qBAAqB,CAAC,QAAQ,CAAC,KAA6C,CAAC,EAAE,CAAC;YAClF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,+BAA+B,CAAC,IAAI,CAAC,KAAK,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,KAAK,EAAE,CAAC,CAAA;QAC1L,CAAC;aAAM,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,EAAE,CAAC,CAAA;IACxG,CAAC;IACD,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAA;IACxK,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,IAAK,KAAK,CAAC,KAAgB,GAAG,CAAC,IAAK,KAAK,CAAC,KAAgB,GAAG,uBAAuB,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAA;IACpN,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAgC,CAAA;QACpD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,kBAAkB,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,IAAK,KAAK,CAAC,WAAsB,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;IACxV,CAAC;IACD,OAAO,KAAyB,CAAA;AAClC,CAAC;AAED,SAAS,SAAS,CAAC,KAAuB;IACxC,OAAO,MAAM,CAAC,WAAW,CAAC,qBAAqB,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAE,CAAC,CAAC,CAAC,CAAA;AACnI,CAAC;AAED,SAAS,OAAO,CAAC,CAAoB,EAAE,CAAoB;IACzD,OAAO,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW,CAAA;AACnF,CAAC;AAED,MAAM,OAAO,0BAA0B;IACpB,OAAO,GAAG,IAAI,GAAG,EAA6B,CAAA;IAE/D,KAAK,CAAC,MAAM,CAAC,MAAyB;QACpC,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,WAAW,EAAE,CAAA;QAClD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;IAClE,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,IAAY,EAAE,WAAmB,IAAmB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,IAAI,WAAW,EAAE,CAAC,CAAA,CAAC,CAAC;IAChH,KAAK,CAAC,IAAI,CAAC,KAAuB;QAChC,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAA;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAA0C,CAAA;QACxF,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;aAC9B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;aACzH,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,IAAI,2BAA2B,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAA;IACtG,CAAC;CACF;AAED,MAAM,OAAO,uBAAuB;IACjB,OAAO,CAA+B;IAC/C,KAAK,CAAgB;IAC7B,YAAY,EAAM,IAAI,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC,UAAU,CAAoB,sBAAsB,CAAC,CAAA,CAAC,CAAC;IAEvF,KAAK,CAAC,aAAa;QACzB,IAAI,CAAC,KAAK,KAAK,CAAC,KAAK,IAAI,EAAE;YACzB,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;YAC7E,KAAK,MAAM,KAAK,IAAI,qBAAqB,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;gBAAE,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,CAAA;QAChI,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC,MAAM,KAAK,CAAA,CAAC,CAAC,CAAC,CAAA;QAC5D,MAAM,IAAI,CAAC,KAAK,CAAA;IAClB,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,MAAyB;QACpC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAA;QAC1B,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IAClI,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,IAAY,EAAE,WAAmB;QAC5C,MAAM,IAAI,CAAC,aAAa,EAAE,CAAA;QAC1B,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA;IACrD,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,KAAuB;QAChC,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAA;QAC7C,MAAM,IAAI,CAAC,aAAa,EAAE,CAAA;QAC1B,MAAM,MAAM,GAA8B,SAAS,CAAC,KAAK,CAAC,CAAA;QAC1D,IAAI,KAAK,CAAC,KAAK;YAAE,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;QAC9I,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,2BAA2B,CAAC,CAAC,OAAO,EAAE,CAAA;IACtI,CAAC;CACF"}