iodine.js is a miniature encrypted messenger: NIP-17 direct messages sealed and gift wrapped per NIP-59, in about 4kb. A form sends a message to someone's DM relays (their kind 10050 list), and a list shows your inbox, unwrapped and rendered like any other xenon feed. It composes with xenon.js, neon.js and radon.js, and signs and encrypts with any signer that has nip44: a NIP-07 extension, chlorine.js, or helium.js with fluorine.js.
<form nio-send nio-to="npub1..."><textarea name="content"></textarea><button>Send</button></form>
<ol nio-inbox nio-with="npub1...">
<template><li><b nrn-profile="{pubkey}" nrn-field="name">{npub}</b> <p>{content}</p></li></template>
</ol>
The form wraps each message twice, once for the recipient and once for you, and the list shows your conversation with that npub, newest first.
| attribute | description |
|---|---|
nio-send |
Form that sends its content field as a private message on submit |
nio-to |
Recipient (npub, nprofile or hex). Falls back to a form field named to |
nio-inbox |
Subscribes to your gift wraps (kinds:[1059], #p:[you]) and renders the unwrapped messages with the element's template |
nio-with |
Only keep messages from or to this pubkey (npub or hex) |
nio-ignore |
iodine skips this element and its descendants |
The inbox sets nxe-filter and nar-ignore on itself and hands over to xenon, so every nxe-* attribute, template field and nxe:* event works as usual, on the decrypted message. It reads from your own kind 10050 relays when you have them, else from the closest nhy-relay or nhyCfg.relays, which are also where messages go when a recipient has no DM relays.
| event | cancel to | detail |
|---|---|---|
nio:send |
skip sending | rumor (unsigned kind 14, editable), recipients |
nio:sent |
rumor, recipients, results per relay |
|
nio:error |
error |
|
nio:message |
skip rendering | wrap, rumor (on the inbox) |
| function | returns |
|---|---|
iodine.send(to, content, relays?) |
promise of results, after wrapping for to and yourself |
iodine.wrap(rumor, pubkey) |
promise of a signed kind 1059 gift wrap for pubkey |
iodine.unwrap(wrap) |
promise of the rumor, or undefined if it is not for you or does not check out |
iodine.relays(pubkey) |
promise of the DM relays in pubkey's kind 10050 list, [] after 3s |
Requires xenon.js and helium.js, fluorine.js for the gift wrap layer, and neon.js for npub recipients.
Extensions ask for permission to decrypt, often once per message. Messages are never cached by argon.js, so the inbox decrypts everything again on every load. Metadata privacy comes from the gift wraps: relays see only random keys, randomized times and ciphertext, though each wrap's p tag names its recipient and relays still see your IP address. Messages have no delivery receipts or deletion.
iodine.js (3.9/1.6kb raw/brotli), prefix nio-, needs hydrogen, helium, fluorine, xenon