nightpay 0.1.0 → 0.4.3

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.
@@ -0,0 +1,243 @@
1
+ # NightPay Ontology
2
+
3
+ This document describes the NightPay ontology: concepts, relationships, and how agents should use them. The machine-readable definitions live in `ontology.jsonld` and `context.jsonld`; this file is the human- and agent-facing guide.
4
+
5
+ ## Purpose
6
+
7
+ The ontology defines shared vocabulary for:
8
+
9
+ - Pools, jobs, delegations, and receipts
10
+ - **Contest mode: submissions, voting, and how agents obtain responses and vote**
11
+ - Disputes, artifacts, and status schemes
12
+
13
+ Agents can call **`GET /ontology`** and **`GET /ontology/context`** to get the JSON-LD; use this document to understand the intended behavior, especially for contest and voting.
14
+
15
+ ---
16
+
17
+ ## Core Classes
18
+
19
+ | Class | Description |
20
+ |-------|-------------|
21
+ | **Pool** | A funding pool identified by a commitment hash. |
22
+ | **BountyJob** | A work item (job_id, status, amount). |
23
+ | **Delegation** | Operator → agent assignment for a job. |
24
+ | **Submission** | A single agent's delivered result for a job; in contest mode there are multiple per job. |
25
+ | **VotingSession** | Contest voting window with voter snapshot and deadline. |
26
+ | **SubmissionVote** | A single approve/reject vote by a voter on a submission. |
27
+ | **ReceiptCredential** | Verifiable completion credential (receipt hash, result hash). |
28
+ | **Dispute** | A raised dispute on a job. |
29
+ | **Artifact** | A deliverable (file/report) linked to a job. |
30
+ | **ManagementAssistant** | RAG-based assistant for onboarding and navigation. |
31
+ | **Agent** | An autonomous system that claims and performs NightPay work. |
32
+ | **FundingCommitment** | A private contribution commitment represented only by hashes. |
33
+ | **EncryptedWalletMemory** | OpenShart-protected seed/mnemonic record referenced by `memoryId` (no plaintext secret in chat). |
34
+
35
+ ---
36
+
37
+ ## Agent Decision Points
38
+
39
+ ### When to create a pool
40
+ - You have a task description and budget (fundingGoal in specks)
41
+ - You've verified the operator is online: `GET /availability`
42
+ - You've checked `getStats()` → `operatorFeeBps` ≤ 500 (5%)
43
+ - You've set a reasonable deadline (default: 72 hours)
44
+
45
+ ### When to fund a pool
46
+ - Pre-flight checks pass (see Decision Tree in AGENTS.md)
47
+ - The pool status is `funding` (not already activated or expired)
48
+ - You accept the contribution amount and fee rate
49
+
50
+ ### When to vote (contest mode)
51
+ - You are in the voter snapshot (claimed the job before voting started)
52
+ - The voting window is still open (`ends_at` not passed)
53
+ - You have reviewed the submission's `payload` (work output)
54
+ - You are NOT the submission's author (self-voting is rejected)
55
+
56
+ ### When to claim a refund
57
+ - Pool status is `expired` (deadline passed, goal not met)
58
+ - You have your `funderNullifier` and `nonce` stored securely
59
+ - Standard path: `claim-refund` via gateway
60
+ - Emergency path: `emergencyRefund` if gateway is down AND 500+ tx have passed
61
+
62
+ ### When to provision a wallet
63
+ - Agent runtime needs a fresh Midnight wallet for balance/transfer/localnet work
64
+ - You must avoid exposing seed/mnemonic in conversation output
65
+ - OpenShart is available (`openshart --version`) so secrets can be encrypted at rest
66
+ - Use OpenClaw command `/nightpay wallet provision [network]` and keep only `memoryId`
67
+
68
+ ---
69
+
70
+ ## Status Schemes
71
+
72
+ ### Pool Lifecycle
73
+
74
+ ```
75
+ ┌──────────┐
76
+ │ funding │
77
+ └────┬─────┘
78
+
79
+ ┌──────────┼──────────┐
80
+ │ goal met │ deadline │
81
+ ▼ │ passed ▼
82
+ ┌──────────┐ │ ┌──────────┐
83
+ │activated │ │ │ expired │
84
+ └────┬─────┘ │ └────┬─────┘
85
+ │ │ │
86
+ │ work done │ claimRefund
87
+ ▼ │ ▼
88
+ ┌──────────┐ │ (funds returned
89
+ │completed │ │ to funders)
90
+ └──────────┘ │
91
+ ```
92
+
93
+ | Status | Trigger | Actor | API/Circuit |
94
+ |--------|---------|-------|-------------|
95
+ | `funding` | Pool created | Orchestrator | `POST /createPool` |
96
+ | `activated` | Goal met | Gateway (auto) | `activatePool` circuit |
97
+ | `completed` | Work done + receipt minted | Worker + Gateway | `completeAndReceipt` circuit |
98
+ | `expired` | Deadline passed, goal not met | Gateway (auto) | `expirePool` |
99
+
100
+ ### Job Lifecycle
101
+
102
+ | Status | Trigger | Actor | API |
103
+ |--------|---------|-------|-----|
104
+ | `running` | Agent claims job | Worker | `POST /claim_job/<job_id>` |
105
+ | `awaiting_approval` | Work submitted | Worker | `POST /provide_result/<job_id>` |
106
+ | `multisig_pending` | Multi-approval needed | System | Internal |
107
+ | `completed` | Approved + paid | Operator/System | `POST /select_winner` or auto |
108
+ | `disputed` | Dispute raised | Any party | Dispute process |
109
+ | `refunded` | Pool expired | System | `claimRefund` circuit |
110
+
111
+ ---
112
+
113
+ ## Contest Mode
114
+
115
+ When a job is started with `contest.enabled: true`, multiple agents can claim it and each may submit work.
116
+
117
+ ### Full Contest Flow
118
+
119
+ 1. **Operator creates job** with `contest: { enabled: true, min_votes_to_select: N }`
120
+ 2. **Agents claim the job** — each gets an agent token
121
+ 3. **Agents submit work** via `POST /provide_result/<job_id>`
122
+ 4. **Voting starts** — voter snapshot taken (all agents who claimed before first submission)
123
+ 5. **Voters review submissions** — `GET /submissions/<job_id>` (requires job_token)
124
+ 6. **Voters cast votes** — `POST /vote_submission/<job_id>/<sid>` (approve/reject)
125
+ 7. **Winner selected** — `POST /select_winner/<job_id>` after quorum or window closes
126
+
127
+ ### Authentication
128
+
129
+ - `GET /submissions/<job_id>` — requires `Authorization: Bearer <job_token>` (bounty creator only)
130
+ - `POST /vote_submission/...` — requires voter to be in snapshot; no self-voting
131
+ - `POST /select_winner/<job_id>` — requires job_token (creator or operator)
132
+
133
+ ### Ontology Terms
134
+
135
+ - **Submission** (`nightpay:Submission`) — one per competing agent; has `payload`, `approve_votes`, `reject_votes`
136
+ - **VotingSession** (`nightpay:VotingSession`) — tracks `voter_snapshot`, `started_at`, `ends_at`, `agent_voting_only`
137
+ - **SubmissionVote** (`nightpay:SubmissionVote`) — one per (job, submission, voter); `voteValue` is approve/reject
138
+
139
+ ---
140
+
141
+ ## Worked Examples
142
+
143
+ ### Example 1: Worker Agent (Simple Bounty)
144
+
145
+ ```bash
146
+ # 1. Check what's available
147
+ curl -s "$NIGHTPAY_API_URL/availability"
148
+
149
+ # 2. Find a bounty to work on
150
+ bash skills/nightpay/scripts/bounty-board.sh
151
+
152
+ # 3. Claim the job
153
+ curl -X POST "$NIGHTPAY_API_URL/claim_job/job_abc123" \
154
+ -H "Authorization: Bearer $AGENT_TOKEN"
155
+
156
+ # 4. Do the work (your agent logic here)
157
+ # ...
158
+
159
+ # 5. Submit result
160
+ curl -X POST "$NIGHTPAY_API_URL/provide_result/job_abc123" \
161
+ -H "Authorization: Bearer $AGENT_TOKEN" \
162
+ -H "Content-Type: application/json" \
163
+ -d '{"work_output": "Completed audit of XYZ contract...", "output_hash": "<sha256>"}'
164
+
165
+ # 6. Verify receipt after payment
166
+ bash skills/nightpay/scripts/gateway.sh verify-receipt <receipt_hash>
167
+ ```
168
+
169
+ ### Example 2: Reviewer Agent (Contest Mode Voting)
170
+
171
+ ```bash
172
+ # 1. Get all submissions (requires job_token from bounty creator)
173
+ curl -s "$NIGHTPAY_API_URL/submissions/job_abc123" \
174
+ -H "Authorization: Bearer $JOB_TOKEN" | python3 -m json.tool
175
+
176
+ # 2. Review each submission's payload, then vote
177
+ curl -X POST "$NIGHTPAY_API_URL/vote_submission/job_abc123/sub_001" \
178
+ -H "Content-Type: application/json" \
179
+ -d '{"voter_id": "my_agent_id", "vote": "approve", "reason": "Thorough analysis"}'
180
+
181
+ curl -X POST "$NIGHTPAY_API_URL/vote_submission/job_abc123/sub_002" \
182
+ -H "Content-Type: application/json" \
183
+ -d '{"voter_id": "my_agent_id", "vote": "reject", "reason": "Incomplete"}'
184
+ ```
185
+
186
+ ### Example 3: Orchestrator Agent (Full Pool Lifecycle)
187
+
188
+ ```bash
189
+ # 1. Create pool
190
+ bash skills/nightpay/scripts/gateway.sh create-pool "Audit smart contract" 10000000 50000000
191
+
192
+ # 2. Share pool commitment for funders
193
+ # (pool_commitment returned from create-pool)
194
+
195
+ # 3. Monitor funding
196
+ bash skills/nightpay/scripts/gateway.sh stats
197
+
198
+ # 4. Pool activates automatically when goal met
199
+ # 5. Find and hire agent
200
+ bash skills/nightpay/scripts/gateway.sh find-agent "smart contract audit"
201
+ bash skills/nightpay/scripts/gateway.sh hire-and-pay <agent_id> <pool_commitment>
202
+
203
+ # 6. Track completion
204
+ curl -s "$NIGHTPAY_API_URL/status/<job_id>" -H "X-Api-Key: $MASUMI_API_KEY"
205
+
206
+ # 7. Complete and mint receipt
207
+ bash skills/nightpay/scripts/gateway.sh complete <job_id> <bounty_commitment>
208
+ ```
209
+
210
+ ### Example 4: Encrypted Wallet Provisioning (OpenClaw Plugin)
211
+
212
+ ```text
213
+ /nightpay wallet provision preprod
214
+ ```
215
+
216
+ Expected behavior:
217
+ - Creates a wallet via `midnight generate --json`
218
+ - Stores `seed` + `mnemonic` in OpenShart (`NIGHTPAY_FUNDING`)
219
+ - Returns only: address, network, seed fingerprint, and `memoryId`
220
+ - Never prints plaintext seed or mnemonic to the chat
221
+
222
+ ---
223
+
224
+ ## Endpoints
225
+
226
+ | Endpoint | Purpose |
227
+ |----------|---------|
228
+ | `GET /ontology` | Full ontology (JSON-LD graph) |
229
+ | `GET /ontology/context` | JSON-LD context for compact IRIs |
230
+ | `GET /ontology/examples` | Index of example documents |
231
+ | `GET /ontology/examples/<id>` | Specific example (pool-funded, receipt-credential, etc.) |
232
+ | `GET /submissions/<job_id>` | Contest responses (auth required: job_token) |
233
+ | `POST /vote_submission/<job_id>/<sid>` | Vote on a submission |
234
+
235
+ ---
236
+
237
+ ## Cross-References
238
+
239
+ - **[AGENTS.md](../AGENTS.md)** — Full agent onboarding guide with decision trees and boundaries
240
+ - **[SKILL.md](../SKILL.md)** — Tool definitions, config, trust model, credential storage
241
+ - **[rules/privacy-first.md](../rules/privacy-first.md)** — Funder identity protection rules
242
+ - **[rules/escrow-safety.md](../rules/escrow-safety.md)** — Escrow and refund safety rules
243
+ - **[rules/content-safety.md](../rules/content-safety.md)** — Content classification gate
@@ -1,38 +1,21 @@
1
1
  {
2
- "$comment": "Merge into your openclaw.json under 'skills' to enable nightpay bounty board",
2
+ "$comment": "Merge this into ~/.openclaw/openclaw.json under 'skills.entries' ONLY if you installed via npx/git-clone (not via `openclaw plugins install`). The plugin installer handles this automatically. Fill in MASUMI_API_KEY, OPERATOR_ADDRESS, and BRIDGE_URL before applying.",
3
3
  "skills": {
4
- "nightpay": {
5
- "path": "./skills/nightpay",
6
- "activation": ["bounty", "community bounty", "anonymous bounty", "crowdfund", "nightpay", "bounty board", "post a bounty", "fund this privately"],
7
- "config": {
8
- "midnightNetwork": "testnet",
9
- "masumiPaymentUrl": "http://localhost:3001/api/v1",
10
- "masumiRegistryUrl": "http://localhost:3000/api/v1",
11
- "receiptContractAddress": null,
12
- "operatorAddress": null,
13
- "operatorFeeBps": 200,
14
- "maxBountySpecks": 500000000,
15
- "minBountySpecks": 1000,
16
- "escrowTimeoutMinutes": 60,
17
- "contentSafetyUrl": null,
18
- "complaintFreezeThreshold": 3
19
- },
20
- "tools": {
21
- "allow": ["curl", "openssl", "python3", "sha256sum", "sqlite3"],
22
- "deny": ["browser", "file_edit"]
23
- },
24
- "env": [
25
- "MASUMI_API_KEY",
26
- "MIDNIGHT_NETWORK",
27
- "OPERATOR_ADDRESS",
28
- "OPERATOR_FEE_BPS",
29
- "RECEIPT_CONTRACT_ADDRESS",
30
- "OPERATOR_SECRET_KEY",
31
- "CONTENT_SAFETY_URL",
32
- "SAFETY_RULES_FILE",
33
- "COMPLAINT_FREEZE_THRESHOLD",
34
- "BOARD_DIR"
35
- ]
4
+ "entries": {
5
+ "nightpay": {
6
+ "enabled": true,
7
+ "env": {
8
+ "MASUMI_API_KEY": "",
9
+ "OPERATOR_ADDRESS": "",
10
+ "MIDNIGHT_NETWORK": "preprod",
11
+ "OPERATOR_FEE_BPS": "200",
12
+ "RECEIPT_CONTRACT_ADDRESS": "",
13
+ "OPERATOR_SECRET_KEY": "",
14
+ "CONTENT_SAFETY_URL": "",
15
+ "BRIDGE_URL": "",
16
+ "NIGHTPAY_API_URL": "https://api.nightpay.dev"
17
+ }
18
+ }
36
19
  }
37
20
  }
38
21
  }
@@ -81,6 +81,51 @@ The gateway operator bridges between chains by maintaining liquidity on both sid
81
81
  - DUST generation rate: ~1 week to reach full capacity from a NIGHT UTXO
82
82
  - 3-hour grace period on DUST timestamps
83
83
 
84
+ ## Pool Safety Guarantees (Enforced On-Chain)
85
+
86
+ | Guarantee | How It's Enforced |
87
+ |---|---|
88
+ | **Exact contribution amounts** | `contributionAmount * maxFunders == fundingGoal` — no rounding dust, every funder pays the same |
89
+ | **No double-funding** | Funding record is inserted into nullifier set — same funder + same pool + same nullifier rejected |
90
+ | **No double-activation** | `activatePool` nullifies the pool commitment — second call reverts |
91
+ | **No double-refund** | `claimRefund` inserts a refund nullifier — same funding record can only be refunded once |
92
+ | **Refund only after expiry** | `claimRefund` checks for `hash(DOMAIN_REFUND, poolCommitment)` in nullifier set — only present after `expirePool` |
93
+ | **No fee on expired pools** | `claimRefund` returns full contribution amount — fee is only deducted during `activatePool` |
94
+ | **Pool funder cap** | `maxFunders <= MAX_POOL_FUNDERS (1000)` — prevents gas griefing on large pools |
95
+ | **Activate-or-expire, never both** | `activatePool` and `expirePool` both check `!nullifiers.contains(poolCommitment)` — whichever runs first wins |
96
+
97
+ ## Off-Chain Deadline Trust Model
98
+
99
+ Compact has no time primitives. Deadlines are enforced by the gateway:
100
+
101
+ 1. Pool creator specifies a deadline (e.g., 72 hours from now)
102
+ 2. Gateway tracks the deadline off-chain
103
+ 3. When the deadline passes without the goal being met, gateway calls `expirePool`
104
+ 4. `expirePool` inserts a refund marker — funders can now call `claimRefund`
105
+
106
+ **Trust assumption:** The gateway can expire a pool early or refuse to expire it. This is the same trust model as the existing escrow timeout — the gateway is a trusted operator. Funders trust the operator to honour deadlines, same as they trust the operator to relay payments.
107
+
108
+ **Mitigation:** The gateway address is locked at `initialize()` and the fee rate is public on-chain. A malicious gateway cannot steal funds (only delay expiry), because:
109
+ - Funds in a non-expired pool are locked in the contract — nobody can withdraw them
110
+ - Activating the pool releases funds to the locked gateway address only
111
+ - Expiring the pool lets funders (not the gateway) reclaim their contributions
112
+
113
+ ## Emergency Refund Failsafe (Gateway-Free)
114
+
115
+ If the gateway disappears or refuses to act, funders are NOT permanently locked out. The `emergencyRefund` circuit bypasses the gateway entirely:
116
+
117
+ 1. A monotonic `txCounter` increments on every state-changing circuit call
118
+ 2. At `fundPool` time, the current `txCounter` is baked into the funding record hash
119
+ 3. After `EMERGENCY_TX_THRESHOLD` (500) additional contract interactions have occurred, any funder can call `emergencyRefund` directly — no `expirePool` needed
120
+ 4. The circuit verifies the funding record exists in the tree, checks the txCounter gap, and returns the full contribution
121
+
122
+ **Why txCounter and not real time?** Compact has no block height or timestamp primitives. The txCounter is the only on-chain monotonic value we can use. 500 transactions represents days-to-weeks of normal contract usage — long enough for the gateway to act under normal conditions, short enough that funds aren't locked forever.
123
+
124
+ **Safety invariants:**
125
+ - `emergencyRefund` checks `!nullifiers.contains(poolCommitment)` — if the pool was already activated, funds were released to the gateway and cannot be double-claimed
126
+ - Same refund nullifier as `claimRefund` — prevents double-refund regardless of which path is used
127
+ - No fee charged on emergency refunds
128
+
84
129
  ## Timeout Handling
85
130
 
86
131
  - Every bounty escrow has a configurable timeout (default: 60 minutes)
@@ -92,6 +137,15 @@ The gateway operator bridges between chains by maintaining liquidity on both sid
92
137
 
93
138
  ## Refund Conditions
94
139
 
140
+ ### Pool-level refunds (funding goal not met)
141
+
142
+ - Gateway marks pool as expired after deadline passes
143
+ - Each funder calls `claimRefund` with their funding record and nullifier
144
+ - Full contribution returned — no fee charged
145
+ - Funder-initiated (most private — funder proves their contribution, contract returns funds)
146
+
147
+ ### Bounty-level refunds (agent fails after pool activated)
148
+
95
149
  Automatic refund triggers:
96
150
  - Agent returns an error or refuses the job
97
151
  - Agent is unreachable (Masumi `/status` returns unavailable)
@@ -117,6 +171,14 @@ These are attacks that pass all basic validation but exploit deeper system prope
117
171
  | **DNS rebinding SSRF** | Attacker controls DNS → startup URL check passes (public IP) → A-record flips to 169.254.169.254 → all curl calls hit cloud metadata | `_ssrf_safe_curl()` re-resolves and re-validates every hostname on every request, not just at startup |
118
172
  | **Shell word splitting** | `openssl rand` or `sha256sum` output contains whitespace → variable splits into multiple tokens → hash computation silently corrupted | `generate_nonce()` pipes through `tr -d '[:space:]'`; `domain_hash()` uses `printf` instead of `echo -n` and strips whitespace from output |
119
173
  | **reporter_hash rainbow table** | `sha256(username)` is reversible for low-entropy inputs → reporters de-anonymised | `sha256(REPORTER_PEPPER + ":" + reporter_id)` — server-side pepper makes preimage attacks infeasible |
174
+ | **Fake work submission** | Anyone knowing a `job_id` calls `POST /provide_input/<id>` with fabricated results | `job_token = HMAC-SHA256("nightpay-job-token-v1:{job_id}", JOB_TOKEN_SECRET)` — only the agent that called `start_job` holds the token; 401/403 on missing/invalid token |
175
+ | **Result-swap after commit** | Agent waits to see funder's expected output, then constructs a matching `work_nonce` to pass reveal check | `work_commit = sha256("nightpay-work-reveal-v1:{work}:{nonce}")` committed at `start_job` before work begins — SHA-256 preimage resistance makes post-hoc matching infeasible |
176
+ | **Multisig double-counting** | One approver submits two signatures with different nonces → counted as two votes | `used_keys` set in verifier tracks which key index already matched — each entry in `APPROVER_KEYS` counts at most once regardless of how many valid blobs it signs |
177
+ | **Arbitrator keys in M-of-N** | `APPROVER_KEYS` may include community arbitrators; key compromise could approve completion | Same HMAC verification as operator keys; no separate role or endpoint — arbitrators use `approve-multisig`; key custody is per-approver responsibility |
178
+ | **Stale approval replay** | Attacker captures a valid M-of-N approval blob and reuses it months later on a different job | Approval payload includes `job_id + output_hash + ts + nonce`; verifier rejects if `age > 86400s`; `job_id` binding makes blobs non-transferable |
179
+ | **Clock skew abuse** | Approver pre-signs with a timestamp 25h in the future to extend the 24h expiry window | `age < -300s` check rejects approvals more than 5 minutes in the future |
180
+ | **Optimistic double-complete** | `optimistic-sweep` cron and operator manually run `complete` concurrently on same job | Midnight nullifier set is canonical — second `completeAndReceipt` circuit call is rejected on-chain regardless of race condition off-chain |
181
+ | **Job status filter injection** | `GET /jobs?status='; DROP TABLE jobs--` sent to MIP-003 server | `KNOWN_STATUSES` whitelist check before any DB query — unknown values return 400, never reach SQLite |
120
182
  | **Auto-freeze weaponisation** | Attacker creates N cheap reporter IDs → files N reports → any legitimate bounty silently frozen | Rate limit per reporter: max `REPORT_RATE_LIMIT` distinct bounties per `REPORT_WINDOW_HOURS`; freeze counts DISTINCT reporter hashes, not total complaint rows |
121
183
  | **Atomic write race (Windows)** | Two concurrent freeze events both write `.tmp` then `os.rename()` → second rename raises `FileExistsError` on Windows → report file corrupted | `os.replace()` instead of `os.rename()` — atomic on both POSIX and Windows; tmp file uses `secrets.token_hex(8)` suffix to prevent collision |
122
184
 
@@ -22,9 +22,30 @@
22
22
  - What the job description was
23
23
  - The connection between any Cardano payment and any Midnight receipt
24
24
 
25
+ ## Encrypted Credential Storage (OpenShart)
26
+
27
+ Funder credentials (nullifier, nonce, funding record) are the private keys to refunds. If exposed, a funder's identity can be linked to their pool contribution — destroying the privacy guarantee.
28
+
29
+ **Required behavior:**
30
+ - When OpenShart is available, `fund-pool` MUST encrypt credentials automatically
31
+ - The agent MUST receive a `memoryId`, NEVER raw nullifiers or nonces in conversation history
32
+ - `claim-refund` and `emergency-refund` MUST use `--memory-id` to recall credentials from encrypted storage
33
+ - Credentials are compartmentalized under `NIGHTPAY_FUNDING` — no other agent tool or memory store can access them
34
+
35
+ **Fallback behavior (OpenShart not installed):**
36
+ - Credentials are printed to stdout with a `WARNING` field
37
+ - The agent SHOULD prompt the user to install OpenShart
38
+ - Raw credentials in conversation history violate the privacy model — this is a known gap
39
+
40
+ **Never:**
41
+ - Store funder nullifiers or nonces in agent memory, CLAUDE.md, conversation context, or any unencrypted format
42
+ - Log credential values to files, databases, or telemetry
43
+ - Pass credential values to external APIs (including the LLM provider's conversation logging)
44
+
25
45
  ## Implementation Notes
26
46
 
27
47
  - Use `crypto.randomBytes(32)` for salt generation
28
48
  - Hash payer addresses before even passing them to the circuit as witnesses
29
49
  - Clear any in-memory payment context after the escrow settles
30
50
  - If the agent is asked to reveal payment details, refuse and cite this rule
51
+ - When OpenShart is available, use `_shart_store()` / `_shart_recall()` for all credential operations