{"id":"8c8201e8a1935bba12927c76af30317fc9c44a4104e4d6888d4d1bd4c1955a9f","pubkey":"b22b06b051fd5232966a9344a634d956c3dc33a7f5ecdcad9ed11ddc4120a7f2","created_at":1780946382,"kind":30817,"tags":[["d","scheduler-dvm"],["title","Scheduler DVM"],["k","5905"],["k","7000"],["k","31990"],["client","nostrhub.io"]],"content":"# NIP Scheduler DVM\n\n`draft` `optional`\n\nThis NIP defines a privacy-preserving protocol for scheduling Nostr events to be published at a future time, using a Data Vending Machine (NIP-90) as the scheduling backend.\n\n## Motivation\n\nPublishing a Nostr event at a future time requires an always-online server. This NIP standardizes the interaction between a client and a Scheduler DVM so that:\n\n- Any client can schedule any signed event without implementing its own server\n- Multiple Scheduler DVMs can exist and compete (self-hostable, open market)\n- No observer or relay can link a scheduled job to its future published event\n- No observer or relay can read the content or the target publication time of a scheduled job\n- The DVM operator can read the scheduled event content and publication time after decryption\n\n## Roles\n\n- **Client**: the Nostr user who wants to schedule an event\n- **Scheduler DVM**: an always-online service that publishes the signed event at the requested time\n\n## Kinds\n\n| Kind | Description |\n|------|-------------|\n| `5905` | Schedule job request (client -> DVM) |\n| `7000` | Job feedback (DVM -> client) |\n| `5`    | Delete / cancel a job (standard Nostr delete) |\n\n## 1. Creating a Schedule\n\nThe client publishes a `kind:5905` job request:\n\n```json\n{\n  \"kind\": 5905,\n  \"pubkey\": \"<client_pubkey>\",\n  \"content\": \"<nip44_encrypt(dvm_pubkey, client_privkey, payload)>\",\n  \"tags\": [\n    [\"p\", \"<dvm_pubkey>\"],\n    [\"encrypted\"]\n  ]\n}\n```\n\n### Encrypted Payload\n\n```json\n{\n  \"job_id\": \"<64 random hex chars>\",\n  \"schedule_at\": 1748000000,\n  \"signed_event\": { \"<full signed Nostr event>\" },\n  \"relays\": [\"wss://relay.damus.io\", \"wss://nos.lol\"]\n}\n```\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| `job_id` | string | yes | 64-char random hex, generated by the client. Stable identifier for this job. |\n| `schedule_at` | unix timestamp | yes | UTC time at which the DVM should publish the event |\n| `signed_event` | object | yes | The complete, already-signed Nostr event to publish. |\n| `relays` | array of strings | yes | Relay URLs where the DVM should publish the event |\n\n## 2. Cancelling a Schedule\n\nThe client publishes a standard NIP-09 delete event tagging the `kind:5905` to cancel:\n\n```json\n{\n  \"kind\": 5,\n  \"tags\": [\n    [\"e\", \"<id of the kind:5905 event to cancel>\"]\n  ]\n}\n```\n\nThe DVM MUST send a `kind:7000` feedback with `status:cancelled` after processing the deletion.\n\n## 3. Listing Schedules\n\nThe client fetches its own job requests directly from relays:\n\n```json\n{\n  \"authors\": [\"<client_pubkey>\"],\n  \"kinds\": [5905]\n}\n```\n\nThe client decrypts each event's `content` locally to reconstruct the list of scheduled jobs and their `job_id`s.\n\n## 4. Job Feedback (DVM -> Client)\n\nThe DVM sends status updates using `kind:7000` with no `p` tag to avoid linking the feedback to the client's pubkey. The `content` is encrypted using a one-time ephemeral keypair that the DVM destroys immediately after use.\n\n```json\n{\n  \"kind\": 7000,\n  \"pubkey\": \"<dvm_pubkey>\",\n  \"content\": \"<nip44_encrypt(ephemeral_privkey, client_pubkey, feedback_payload)>\",\n  \"tags\": [\n    [\"r\", \"<job_id>\"],\n    [\"ephemeral-pubkey\", \"<ephemeral_pubkey>\"]\n  ]\n}\n```\n\n### Feedback Payload (decrypted content)\n\n```json\n{\n  \"status\": \"scheduled\",\n  \"message\": \"<optional human-readable string>\"\n}\n```\n\n| Status | Description |\n|--------|-------------|\n| `scheduled` | DVM has accepted and queued the job |\n| `published` | The event has been broadcast to the requested relays |\n| `failed` | All relays rejected or were unreachable |\n| `cancelled` | The job was cancelled via `kind:5` |\n| `error` | The job request was invalid |\n\n## 5. DVM Discoverability (NIP-89)\n\nScheduler DVMs SHOULD announce themselves using NIP-89:\n\n```json\n{\n  \"kind\": 31990,\n  \"pubkey\": \"<dvm_pubkey>\",\n  \"content\": \"{\\\"name\\\": \\\"My Scheduler DVM\\\", \\\"about\\\": \\\"Schedule any Nostr event.\\\"}\",\n  \"tags\": [\n    [\"k\", \"5905\"],\n    [\"t\", \"scheduler\"]\n  ]\n}\n```","sig":"ad50af01e9be16b1b7ab66e63df6dfa4adcfbc05cc5532b3af3cb52b2e30903d8d0cab7d829f4ea933bb5c4777cebb5941a6bd0cca517b58dc7588651b06dfe4"}