Privacy data map
Status: Built, runs on simulatorStatus: Coming soon
The backend data map separates public chain records, server-held material and material intended only for a reader’s device.
Payment amounts are public onchain in this version. The report verifier sees the attested income figure and prior opening briefly in memory. Private payouts are coming soon. This inventory describes the pinned backend design and its local implementation; it does not establish a production deployment or a completed independent audit.
Tier-one exception: a series using the onchain income router exposes routed income, its threshold and rate per unit onchain. The backend rows about device-only income and encrypted terms do not hide those public router fields. See who sees what for that separate chain path.
Reading the map
“Vault” means server-key encryption. “e2e” means client encryption for the addressed attestor; the server holds ciphertext. These are different trust boundaries. Admin access to ciphertext is not a decryption key. The upstream document’s owner, issuer, holder, backer, attestor, admin and public labels identify readers.
Retention is not implemented as a deletion promise. The source says time limits remain undecided and nothing is deleted automatically. The “Deleted when” column records intended rules. Chain records persist; requesting account deletion does not erase them.
Income and the things derived from it
Source: stage2:PRIVACY.md#L48.
| Field | Where | Storage | Readers | Intended deletion |
|---|---|---|---|---|
| Income figure (year to date, per source) | the issuer's device only: its income chain keeps each reported quarter's year-to-date figure and salt (the opening the next quarter's proof chains from). The report verifier sees the figure inside the zkTLS proof, and the prior quarter's opening, in memory only; a tier-3 attestor receives the opening end-to-end encrypted | none on the server | — | never exists at rest on the server |
| Income salt | device; derived from a device secret (HMAC-SHA512 under the device secret over the series and period, reduced mod the BN254 field order; @merces/poi-device deriveIncomeSalt), so a reinstalled device rebuilds its openings from its own records and no server-side copy exists (owner question 2 open since S1, the default). Sent to the verifier in memory only (tier 2) or end-to-end encrypted to the attestor (tier 3) | none | — | — |
| Income commitment and the prior it chains from | reports.income_commitment, reports.prior_income_commitment, periods, series.last_income_commitment, onchain ReportAccepted | hash (Stage 1 Poseidon(2) over the year-to-date figure and the salt) | issuer, holders, public (onchain) | — |
| Payment amounts (gross, fee, net, accrual) | reports, periods, money_events, fees_ledger, onchain | plain (public onchain in v1) | issuer, holders, admin; never in the public activity feed | end of retention |
| Threshold θ, rate r (and the share rate it comes from; on a curve series r is per base lamport, units offered × 10^6, S3, derived in memory by the terms service and the issuer's device) and the terms salt | terms_vault.ciphertext; terms_amendments.ciphertext | vault | issuer, eligible backers (every read logged in terms_access_log; eligibility read live); the reviewing attestor on an amendment. No admin bypass. Never in an error message or a log: a share rate a WAD cannot hold is refused naming the rule only (A-TERMS-5), and the logger censors theta, rWad, shareRate, ratePerUnitWad and termsSalt | series end + retention |
| Terms commitment | series, series_drafts, onchain | hash (Stage 1 Poseidon(5) over θ, r, the currency code, the indexing rule and the terms salt) | public | — |
| zkTLS proof (contains the figure) | never on the server; device → verifier in memory | none | — | — |
| Report proof + public inputs | reports.proof: Stage 1's Groth16 proof and its six public signals (terms commitment, units outstanding, FX rate, prior and income commitments, payment); never the witness, which stays inside the device's prover | plain | issuer, holders, admin | end of retention |
Verifier attestation (since S4 WP6 Stage 1's REPORT signature {signer, signature}: the verifier key's secp256k1 signature over the chain's signing domain, the series key, the period, prior and income commitments, tier 2 and the liquidity flag; no figure, no source) | reports.attestation, onchain (an argument of submit_report) | plain (public onchain) | issuer, holders, admin | — |
| Report verifier keys (20-byte addresses, registration and revocation times) | verifier_keys (migration 024), onchain registry | plain (public onchain) | admin (GET /admin/verifier-keys); public onchain | — |
Tier-3 attestation (since S4 WP9 Stage 1's REPORT signature with tier 3 by the case's attestor key: {signer, signature, digest} over the chain's signing domain, the series key, the period, prior and income commitments, tier 3 and the liquidity flag; no figure, no salt, no opening) and the signing request it answers (GET /attestor/tier3/{caseId}/message: the same public fields) | tier3_cases.attestation, reports.attestation ({signer, signature}), onchain (an argument of submit_report) | plain (public onchain once submitted; only the series' issuer can submit a report, so an unsubmitted one is no bearer authorisation) | issuer, the case's attestor, admin; holders through the report and onchain | end of retention |
FX fallback signatures (since S4 WP9 Stage 1's FX signature per series: the attestor key's {signer, signature, digest} over the chain's signing domain, the series key, the period and the rate; a public rate, no income) | fx_fallback_signatures (migration 026), reports.fx_fallback ({signer, signature}), onchain (fx_fallback_sig, fxFallbackSignature) | plain (public onchain once submitted; only the series' issuer can submit the report) | attestors on the request's chain, the series' issuer (GET /series/{seriesId}/reporting/fx-fallback/{periodId}), admin | end of retention |
| Tier-3 income opening (year-to-date figures and salts of the quarter and its prior) | tier3_cases.opening_ciphertext | e2e (a merces-e2e v1 envelope; anything else is refused) | the chosen attestor only, to check the commitments against the documents; server: nobody. The attestor's signed case carries commitments, the proof and the attestation only | case closed + retention |
| Tier-3 documents (tax returns, accounting exports) | tier3_cases.documents_ciphertext, tier3_annual.documents_ciphertext | e2e | the chosen attestor only (client-side decryption); server: nobody | case closed + retention |
| Tier-3 attested payment | tier3_cases, tier3_annual (amounts only) | plain | issuer, attestor, admin | end of retention |
Annual TRUEUP signature (S4 WP13: the attestation key's Stage 1 TRUEUP signature, the true-up nonce, the attestation time and the signed adjustment it binds, its digest; the adjustment is a payment figure Stage 1 publishes onchain with submit_trueup, never income) | tier3_annual.trueup_signature, .trueup_signer, .trueup_nonce, .attestation_ts, .adjustment, .trueup_digest (migration 030), audit_log (signer, nonce, time, digest; never the signature), onchain once the issuer submits it (S5) | plain (S4 amendment D2: not public until onchain; only the issuer can submit it) | the signature: nobody through the API (stored for the issuer's submit_trueup, S5); its signer, nonce, time, adjustment and digest (trueUp): the attestation's key holder, the issuer and admins; never in the activity feed or a notification | end of retention |
| Income source kinds (stripe/deel/…) | applications.income_source_kinds, income_sources.kind | plain | owner, admin | account deletion |
| Report source kind (stripe/deel/…) | reports.source (never in the verifier attestation or onchain) | plain | issuer, admin (holders get report rows without it) | end of retention |
| zkTLS session source kind | zktls_sessions.source | plain | nobody through the API (written at session start, never served) | end of retention |
| Source credentials / API keys | never (zkTLS sessions run on the device with the provider) | none | — | — |
Hardship and personal circumstances
Source: stage2:PRIVACY.md#L73.
| Field | Where | Storage | Readers | Intended deletion |
|---|---|---|---|---|
| Hardship reason and evidence | hardship_requests.ciphertext | e2e | the chosen attestor only; server: nobody | decision + retention |
| Hardship state and dates | hardship_requests (state, until, decided_at; withdrawn since S4 WP10), onchain PauseRequested/Approved/Ended/RequestWithdrawn | plain | issuer, holders (status and dates only), attestor, admin | — |
Pause nonce and the relayed approval (S4 WP10: the pause nonce of the onchain request; the attestor key's Stage 1 PAUSE signature {signer, signature, digest, pauseNonce, pauseEnd} and the keeper's relay transaction; no reason, no figure) | hardship_requests.pause_nonce, .approval, .relay_tx (migration 027), audit_log (signer, nonce, digest, relay transaction), onchain (approve_pause's arguments) | plain (public onchain: stored only once the chain accepted it) | the request's attestor key holder and admins (GET /attestor/hardship/{id}, the decision's response); never in the holders' status view, the activity feed or a notification | — |
| An unrelayed PAUSE signature (a bearer authorisation until used or its nonce moves, S4 amendment D2) | never stored: a refused or failed relay stores nothing; the idempotency record keeps the hash of the decision's body, and the response of a relayed one | — | — | — |
| Decision code | hardship_requests.decision_code, audit_log | plain (codes only) | attestor, admin | — |
| Approval refusal | API response only (detail.stage1: Stage 1's rule name; the message names the rule and the protocol bound, never a date); nothing stored, recorded in the audit log or notified | plain (codes only) | the deciding attestor | — |
| Extinguishment evidence | extinguishment_requests.evidence_ciphertext | e2e | the two attestors; server: nobody | — |
Extinguishment signatures (S4 WP12: the two named keys' Stage 1 EXTINGUISH signatures {signer, signature, digest}, the nonce they bind, the keeper's relay transaction and the Stage 1 name of a refused relay; no evidence, no figure) | extinguishment_requests.signatures, .extinguish_nonce, .relay_tx, .relay_error (migration 029), audit_log (signer, nonce, digest, relay transaction), onchain once relayed (extinguish's arguments) | plain; an unrelayed signature is a bearer authorisation (S4 amendment D2) | before the relay: no read shows a signature (the named keys' holders and admins see which keys signed, signers); once relayed (public onchain): the named keys' holders and admins (GET /attestor/extinguish/{id}, the relay's response); never in the activity feed or a notification | — (a signature over a nonce that moved, or by a key no longer active, is dropped) |
Identity and accounts
Source: stage2:PRIVACY.md#L86.
| Field | Where | Storage | Readers | Intended deletion |
|---|---|---|---|---|
| Wallet addresses | account_wallets, eligibility, allowlist (indexed), onchain | plain | owner; public onchain | account deletion (chain data persists) |
| Email address | accounts.email | plain | owner, the email channel | account deletion / on request |
| Display name, country | accounts, applications.country | plain | owner, admin | account deletion |
| Identity documents | never (the identity-check provider holds them) | none | — | provider's policy |
| Identity check id and status | applications.identity_check_id, identity_status | plain | owner, admin | application retention |
| Attestor identity check id and status (A-SIG-7, migration 031) | attestor_identity_checks (the provider's check id, its status, when started); one row per account | plain | owner (GET /me/attestor/identity-check), admin (the status only, GET /admin/attestors identityStatus); never public, never in a notification or the activity feed | account deletion (the provider holds the documents, under its policy) |
| Application notes | applications.notes_encrypted | vault | owner, admin | application retention |
| Persona | applications.persona | plain | owner, admin | — |
| Readiness report | applications.readiness | plain | owner, admin | — |
| Decision reason | applications.decision_reason_code, audit_log | codes only | admin (owner sees the code) | — |
| Attestor key links (S4 WP7) | account_signer_keys (a key's 20-byte address, the account, when linked); audit_log (signer-key.link / .unlink) | plain | owner, admin (GET /admin/attestors); never public, never in a notification, the activity feed or another account's view. The key's address and its registration are public onchain (GET /chains/{chain}/signer-keys shows no account) | unlink (409 while work addressed to the key is open) / account deletion |
| Attestor private key (S4 WP8) | the attestor's own machine only: the Merces attestor tool's Web3 Secret Storage v3 keystore (~/.merces/attestor/keystore.json, mode 0600; scrypt n ≥ 2^18, AES-128-CTR) | encrypted under the attestor's password; never sent, stored or logged by Merces (the tool posts only signatures and the link proof, and prints no key) | the attestor only | the attestor deletes the file (a lost keystore or password is a lost key: rotate it) |
| Sign-in nonces and key-link challenges | auth_nonces (purpose sign-in or link-signer-key; a link challenge names the account and the key and no chain) | plain | nobody (consumed) | 10 minutes (expiry); purge weekly |
| Session token | client only (HMAC) | none | — | 24 h expiry |
Contracts and terms
Source: stage2:PRIVACY.md#L105.
| Field | Where | Storage | Readers | Intended deletion |
|---|---|---|---|---|
| Signed HEU contract and charter | contract_sessions.signed_document_encrypted | vault | the signer, admin (every read recorded in the audit log) | legal retention |
| Contract hash | contract_sessions.signed_hash, series.contract_hash, onchain | hash | public | — |
| Rendered (unsigned) documents | returned once to the signer; not stored | none | — | — |
| An amendment's AMEND signature (S4 WP11: the attestor key's Stage 1 signature over the series' amendment nonce, the new terms commitment and the new cap; no term in plain, no figure) with its nonce and digest | terms_amendments.attestor_signature, .amendment_nonce, .digest (migration 028), audit_log (signer, nonce, digest; never the signature), onchain once it lands (amend_terms's arguments) | plain (only the issuer can submit it: amend_terms is the issuer's transaction) | the issuer, in its built amendTerms only, until it lands; the nonce and digest in the amendment list (issuer, the series' chain's attestors, admins); never in the activity feed or a notification (S4 amendment D2) | dropped when the signature dies (the nonce moved or the key is no longer active: the amendment awaits an attestor again) |
Bids and holdings
Source: stage2:PRIVACY.md#L114.
| Field | Where | Storage | Readers | Intended deletion |
|---|---|---|---|---|
| Bid price, quantity, salt | client; revealed onchain at reveal time | none until reveal (public afterwards). The commitment is Stage 1's keccak over the price, quantity, salt and wallet and binds no series or window (S4, A-SIG-8), so the client draws a fresh 32-byte salt for every bid and order: a reused salt would make equal bids in two series commit identically and link them | — | — |
| Reveal backup | bid_backups.ciphertext (plaintext fields rejected) | client-encrypted | owner; server: nobody | after settlement + retention |
| Commitment, deposit | bids, onchain | plain | owner, issuer; public onchain | — |
| Auction settlement: rounds and rejections, the clearing price, units sold, proceeds, the failure reason, the mint gate | series, settlement_rounds, onchain | plain | public (GET /series, GET /series/{id}/auction; onchain); the activity feed names rounds, reasons and unit counts only, never an amount (the indexer's summary()) | — |
| A bid's validity, processing and delivery state, and what it is owed (pending units and refund) or was delivered | bids, onchain | plain | owner (/me/bids); public onchain: Stage 1 keeps every bid's state in a public account, and anyone may deliver any bid (deliverBid pays the bidder only); the public preview projects each revealed bid's processing | — |
| A bid's non-reveal charge (NRC: what an unrevealed bid paid the fee collector when its raise settled) | bids.non_reveal_charge, fees_ledger (kind nonReveal), money_events, onchain | plain | owner (/me/bids), admin (accounting); public onchain (Stage 1's NonRevealCharged, amount-bearing); the activity feed and the bidder's notice name no amount; not income | — |
| Positions, claims (S3: Stage 1's claim family) | positions, money_events, onchain | plain | holder, issuer, admin; public onchain (PayoutClaimed, TailClaimed, FinalClaimed, RedemptionClaimed, UnitsBurned; none is income). A position row, and so holder access, comes only from an allocation, a window fill or, on a curve, a stake or a tender: never from a claim (a claim of nothing emits its event with zeros) or a holder registration, so neither grants holder access (A-REV-10). A claim of 0 writes no money row. The claim events are amount-bearing: the activity feed never names an amount | — |
| Curve census, stakes, tenders and proceeds (S3: the unit mint's supply and protocol-held balances, each holder's stake, the stake and tender totals, the collected proceeds and trading fees) | curve_census, positions.staked_units, series.staked_total/tendered_total, curves, money_events, fees_ledger, onchain | plain | public onchain (Stage 1's CurveUnitsSynced, UnitsStaked, UnitsUnstaked, the stake vault, a tender's RedemptionClaimed and UnitsBurned, CurveActivated, CurveProceedsCollected, TreasuryFunded, FeeTaken{curveTrading}); the amount events among them are amount-bearing; the activity feed names unit counts only, never an amount or a holder; none of it is income | — |
| A wallet's curve position (S3 WP12: wallet and staked units, pending and claimable payout, what it can stake, the tender) | read live from the chain per request (GET /series/{id}/curve/position); not stored | none | the account that linked the wallet (account tier; every curve builder takes one of the caller's wallets), and admin for any wallet it names (the admin bypass of every wallet builder; the position holds no income data, only unit counts, payouts and the allowlist flag, all public onchain, and a transaction built for a wallet needs that wallet's signature; a live read of chain-public data, so not recorded in the audit log, like admin's other reads of public chain state); public onchain as balances and holder records | — |
The buyer's acknowledgement of the statements before a buy (S3 WP12: acknowledged, beforeYouBuyVersion) | the build-swap request only; checked against the current version, not recorded | none | — | — |
Operational data
Source: stage2:PRIVACY.md#L129.
| Field | Where | Storage | Readers | Intended deletion |
|---|---|---|---|---|
| Audit log (actor, action, target, codes) | audit_log (append-only) | plain | admin | never (immutable) |
| Terms access log | terms_access_log | plain | issuer, admin | series end + retention |
| Notifications (statuses, ids, dates) | notifications, email_outbox (mock) | plain | owner; admin views are recorded in the audit log | read + retention |
| Curve keeper records (S3 WP11: each lifecycle check that acted or read a change, with the pool's public reserve and the chain time; the curve reminders sent, by wallet, series, payment round and next step) | curve_monitor, reminders_sent | plain | public onchain facts; a reminder goes to the account of the one linked wallet it names, never to the issuer, and names no amount, price or count (templates read the whitelisted keys only; the wallet's step is a fixed code); keeper errors carry codes only | — |
| Outbox payloads | outbox | plain (may carry amounts owed to the issuer, and an auction event's public onchain amounts: a delivered refund, proceeds; never income) | admin | after dispatch + 30 days |
| Logs | process stdout | redacted (income-bearing keys censored at the sink, with Stage 1's openings and private terms: incomeSalt, priorSalt, termsSalt, priorIncomeYtd, theta, rWad, shareRate, ratePerUnitWad, opening, priorOpening, tier3Opening, deviceSecret); error messages carry no values | operators | log retention |
| Keeper runs, reconciliation runs | keeper_runs, reconciliation_runs | plain | admin | 90 days |
| Delivery retries (series, bidder, attempts, next attempt, Stage 1 error name or code; never a message) | auction_deliveries | plain | admin | when the bid is delivered |
Signatures and keys (S4)
Source: stage2:PRIVACY.md#L173.
| Field | Where | Storage | Readers | Intended deletion |
|---|---|---|---|---|
| The attestor and verifier key registry (20-byte addresses, active, registration and revocation times) | attestors, verifier_keys (indexed from the chain; migrations 023, 024), onchain | plain (public onchain) | public (GET /chains/{chain}/signer-keys: address, active, revokedAt, graceUntil; no account); admin (GET /admin/attestors, GET /admin/verifier-keys) | — |
| Attestor proposals (a chain, a key, the account it was proposed for, register or revoke) | attestor_proposals, admin_tx_requests, audit_log | plain | admin | — |
| A signing request (S4 WP9-WP13: kind, chain, series, series key, domain, the addressed key, the typed fields, pre-image, digest, validity) | API response only; never stored | none | the addressed key's account, admins (the routes' tiers) | — |
What can go wrong
A private document can still reveal facts through a public payment, a disclosed wallet or a compromised device. Server-key encryption and attestor-only encryption expose data to different parties. Retention deadlines remain undecided, and the source inventory does not make an automatic deletion workflow exist.