Documents Pallet
pallet-pilier-documents holds the chain-wide, deduplicated table of evidence-file fingerprints
a digital product passport can cite as proof — certificates, lab reports, compliance documents.
It is a separate pallet from the Passport Pallet, not a section of it: the
passport pallet never keeps its own file table, and only ever asks this pallet, through the
DocumentsAccess trait, whether a cited fingerprint is registered.
The table is deliberately not owned by a project or scoped to it: the same certificate can back passports from several projects, so it is stored once, keyed by its own content fingerprint — exactly as the passport pallet's own head-record table deduplicates by fingerprint rather than by publisher.
What a registration proves
The only accurate claim a registration supports is: a file with this exact content existed no later than the block it was registered in. It never proves that whatever currently sits at the file's address is unchanged — the address is controlled by whoever published the file, who can replace or remove what is served there at any time, and this pallet has no way to detect that. The fingerprint, not the address, is the proof; the address is only where a copy can currently be fetched from.
The file table
Reading documents.files(fingerprint) returns a FileInfo:
| Field | Type | Meaning |
|---|---|---|
registered_by | u32 (project id) | The project that registered this fingerprint. Names who is responsible for the entry — it does not make the table project-owned. |
storage_endpoint_id | u32 | Which storage endpoint (from the Registry Pallet) this file's address is relative to. |
path | Vec<u8>, up to 256 bytes | The part of the file's address past the storage endpoint's own common address part. |
content_type | Vec<u8>, up to 128 bytes | The file's content type (for example, a MIME type). |
registered_at | block number (u32) | The block this fingerprint was first registered in. |
Assembling the file's address: concatenate the storage endpoint's own address (read from
registry.storageEndpoints(storage_endpoint_id) — see the Registry Pallet)
with this record's own path. Neither half is enough on its own; together they are the address
a copy of the file can currently be fetched from — again, not proof that it still serves the
same content, only a place to look.
The fingerprint itself is 32 bytes, computed by the caller from the file's content before
calling register_file; this pallet never computes it and never inspects the file itself.
Calls
register_file(project_id, fingerprint, storage_endpoint_id, path, content_type)
Registers a file's fingerprint. Fails with:
NotProjectMember— the caller is not a member ofproject_id(checked against the Registry Pallet; see "Who can write" below).StorageEndpointNotFound—storage_endpoint_iddoes not name an existing storage endpoint.FileDataMismatch— this fingerprint is already registered with differentstorage_endpoint_id,pathorcontent_type. Registering it again with the same data is accepted as a no-op: the existing entry, including its originalregistered_by, is left exactly as it was.
On a genuinely new registration, charges the current DocumentPrice (see "Price" below) before
writing anything, and refunds the standard transaction fee on success. A no-op re-registration,
or a failure, leaves the standard fee charged as normal.
set_price(new_price)
Changes DocumentPrice. Restricted to the council (AdminOrigin).
Price
DocumentPrice is a single value in this pallet's own on-chain storage, charged only when
register_file actually creates a new entry. Read the value currently in force yourself:
const { ApiPromise, WsProvider } = require('@polkadot/api');
const api = await ApiPromise.create({ provider: new WsProvider('wss://rpc.pilier.dev') });
console.log((await api.query.documents.documentPrice()).toHuman());
Changed only by a council vote through set_price — never by a runtime upgrade.
Who can write
register_file checks that the caller is a member of the named project — the same
project-membership mechanism the Registry Pallet uses everywhere else on
this chain (a project's owner, plus any account the owner has added as a writer). A project only
has anything to register once it has been granted a company registration number by the council,
so in practice this pallet's write access follows the same short, council-approved list the
passport pallet's does: today, only Validité.
Worked example
The evidence file behind this page's worked passport record (see the
Passport Pallet page) was registered under project 3, storage endpoint 2
(address https://example.invalid/passport-docs-example/), path certificate-1.pdf, content type
application/pdf. Its fingerprint is 0x1e28ba7ac941ad264440690254d7524f0c0259cfd4c11b1711c3d38b07249d3a.
Reading it back:
const { ApiPromise, WsProvider } = require('@polkadot/api');
async function main() {
const api = await ApiPromise.create({ provider: new WsProvider('wss://rpc.pilier.dev') });
const fingerprint = '0x1e28ba7ac941ad264440690254d7524f0c0259cfd4c11b1711c3d38b07249d3a';
console.log((await api.query.documents.files(fingerprint)).toHuman());
await api.disconnect();
}
main().catch(console.error);
This returns registeredBy: 3, storageEndpointId: 2, path: "certificate-1.pdf", contentType: "application/pdf", registeredAt: 891989 — assembling its address means reading storage endpoint
2 from the registry pallet and appending certificate-1.pdf to its own address.