9.8 KiB
Executable File
Private pilot deployment: mock-only foundation
Status: application and isolated PostgreSQL sandbox only. This is not a real-XMR stack, a public node, or an approval to accept deposits. The operator already hosts a Monero full node on the network and intends the pilot to reuse it; this Compose profile neither starts nor connects to that daemon yet. Wallet-RPC, observer, deposit enrollment, and withdrawal execution are not included.
Start the isolated profile
Use compose.custody-pilot.yaml with the stable project name wikideck-xmr-pilot. Do not combine it with the existing local custody sandbox, the disposable PostgreSQL fixture, or another pilot project name. Project-scoped volumes hold the pilot's PostgreSQL database, credentials, and node state separately.
Build/start with docker compose -p wikideck-xmr-pilot -f compose.custody-pilot.yaml up -d --build. Open http://127.0.0.1:3423/ and confirm the page identifies PRIVATE PILOT. The service binds to loopback by default; set WIKIDECK_PILOT_PORT only when changing the local port.
The app and browser bundle both select the pilot identity. Startup requires WIKIDECK_PILOT_MODE=private-mock-only; payments are fixed to mock. The market sandbox and PostgreSQL schema are freshly bound to this distinct chain ID. Do not point this stack at existing testnet/mainnet node or custody volumes.
To stop while preserving pilot data, run docker compose -p wikideck-xmr-pilot -f compose.custody-pilot.yaml down without volume removal. Do not use --volumes unless the database, credentials, and node history are intentionally being destroyed.
What this enables—and does not
The independent private chain uses a fixed pilot genesis time and its own deterministic chain/genesis identity. The app can run with the current sandbox features and mock balances while test cards and UI flows are exercised. Testnet and mainnet candidate identities and launch gates remain unchanged.
This stack does not connect to the operator's Monero daemon, create real Monero deposit addresses, scan a wallet, approve real credits, construct or relay withdrawals, or settle NFT sales against active consensus. Having a full node supplies only the blockchain daemon; it is not a Wallet-RPC service, watch-only deposit wallet, or spending wallet. Deposit enrollment and withdrawal request flags remain rejected outside the mainnet profile, and mainnet remains separately gated. Do not expose the service publicly or solicit funds.
Pilot custody identity and durable deposit reservations (schema v11)
Schema v10 added the immutable identity binding with separate fields for Wikideck market network/chain (wikideck-randomx-pilot-1 and pilot chain ID) and Monero network (mainnet). Schema v11 adds a separate pilot reservation table. A signed beneficiary/wallet request can now reserve a never-reused subaddress index transactionally, with request replay, same-beneficiary recovery, concurrent index allocation, and conflict protection. Mainnet allocation/withdrawal constraints and v1-v10 migration definitions remain unchanged.
The reservation table has a composite foreign key to the immutable identity row, so direct SQL inserts cannot bind a reservation to a different custodian, Wikideck chain, or Monero network. The focused disposable PostgreSQL lifecycle test verifies index sequencing, replay, concurrent reservations, beneficiary conflict, and denied financial/address access for the restricted role. This implementation still does not create any address.
Use a separate fresh custody database for this identity exercise. The existing pilot Compose database contains mock sandbox custody and is not eligible; importing or reinterpreting its mock journals as Monero liabilities is prohibited. An operator migration is explicitly gated; identity initialization also refuses populated financial, mock, observer, allocation, credit, release or withdrawal tables.
After an operator creates/provisions a distinct empty database and its schema-owner connection, the CLI workflow is:
- With the
pilotprofile selected, runcustody:db migrate-pilot-schemawithWIKIDECK_PILOT_SCHEMA_MIGRATION=explicit-pilot-schema-migration-only. - Run
custody:db initialize-pilot-custody-identity <CUSTODIAN_UUID>withWIKIDECK_PILOT_MODE=private-mock-only,WIKIDECK_PILOT_CUSTODY_IDENTITY=initialize-pilot-custody-v1, andWIKIDECK_PILOT_CUSTODY_OPERATIONS=identity-only-no-funds. - If using the reservation library against that database, create a separate least-privilege role out of band and run
custody:db grant-pilot-deposit-reservation-runtime <ROLE>withWIKIDECK_PILOT_CUSTODY_OPERATIONS=reservation-only-no-addresses.
The schema migration does not grant runtime roles. The reservation role can read schema-version digests and the immutable pilot identity, execute a fixed isolation predicate, and read/insert immutable reservation rows. It cannot read custody metadata, balances, journals, mock custody, observer data, withdrawal intents, or alter reservation history. The isolation predicate uses a fixed-search-path schema-owner function; the role does not receive broad SELECT access just to check that the dedicated database remains unpopulated.
The CLI refuses other custody actions on the pilot profile; mainnet keeps its existing separate controls.
The library path verifies the signed pilot request, checks both network identities and configured wallet-generation/fingerprint/account, then reserves an index. It intentionally stops there: no Wallet-RPC call, address creation/registration, address response, observer, credit, withdrawal, or HTTP enrollment route exists. A durable reserved index is never reclaimed; wallet-side index advancement or lost RPC responses must be reconciled before address-generation support is added. Existing mainnet enrollment remains unchanged. Do not treat a reserved index or valid signature as approval to accept funds.
Reusing the operator-hosted daemon
The next integration should point the observer/gateway to the existing daemon rather than deploy or synchronize another one. An operator-triggered, read-only get_info probe is now available as a first connectivity check. It checks the expected Monero network, synchronized/local height, and daemon status; it does not open a wallet or provide a financial proof. The probe is opt-in and does not run on app startup.
Create a protected JSON config file outside the repository with network set to mainnet (or stagenet for a rehearsal), url set to the daemon's private literal-IP /json_rpc endpoint with its explicit port, and allowPrivateHttp set to true only when deliberately using HTTP on a trusted private network. For example:
{
"network": "mainnet",
"url": "http://192.168.1.20:18081/json_rpc",
"allowPrivateHttp": true
}
Replace the example address with the operator's actual daemon address. Protect the file with owner-only permissions/ACLs. No credentials belong in this file. The endpoint is deliberately restricted to a private literal IP; public hosts and DNS names are rejected.
From the pilot Compose project, run docker compose -p wikideck-xmr-pilot -f compose.custody-pilot.yaml --profile monero-check run --rm daemon-check; set WIKIDECK_MONERO_DAEMON_CONFIG_HOST to the config file path if it is not at the documented default. This runs from the Compose network, making it a better reachability check than testing only from the host. It emits the network, height, version and result, but not the endpoint. The current probe does not implement Monero's Digest --rpc-login; if the daemon requires it, do not disable auth or publish RPC to make this check work. Put a separately reviewed, authenticated TLS proxy on the private path or defer the check until a secure supported transport is configured.
Before wiring it into observer/gateway services, also verify the daemon's software version and operation policy. Mainnet is required for the real-XMR run; Stagenet rehearsal needs a separate Stagenet daemon endpoint. A daemon that is reachable on the operator's LAN is not automatically reachable from Docker containers.
Keep daemon RPC private to the pilot/operator network, bound to an appropriate private interface, and firewall-allowlisted to the pilot host. Prefer a supported authenticated TLS boundary; do not publish daemon RPC or Wallet-RPC to the Internet. The probe's HTTP option has no credentials and relies on private-network isolation; it is only for an explicitly trusted/firewalled route. Store any later gateway credentials/certificates in protected secret files, never in source, Compose literals, or browser settings. The observer should use a watch-only wallet/RPC boundary; transaction signing and spend keys remain separate and out of the app/observer. The eventual flow still needs a private Wallet-RPC gateway, observer, schema/role integration, reviewed credits, actual withdrawal execution, recovery, and active sale settlement, as described in the implementation plan.
This probe does not establish that the node is honest or independently canonical, and synchronized: true is a point-in-time daemon report—not a finality guarantee or credit authorization. No deposit addresses are issued and no financial API is enabled by a successful result.
Initial checks
The network manifest reports profile: pilot, network: wikideck-randomx-pilot-1, and launchStatus: private-pilot-mock-only. Verify startup refuses a missing pilot-mode opt-in and any non-mock payment setting. Deployment tests check loopback binding, distinct profile selection, database secret isolation, and absence of an in-stack Monero daemon. A successful mock deployment is only evidence for this first slice, not for daemon connectivity or real-XMR readiness.