Why Nostr? What is Njump?
2025-03-10 09:31:19

Wiki
nip-f1

Interactive Templated Content Frames

Motivation

Current content display in Nostr is either unstructured (plain text/markdown) or client-dependent. This makes it difficult to create consistent, interactive experiences across different clients. Hyperframes solve this by providing:

  1. Guaranteed rendering consistency through HTML/CSS templates

  2. Interactive capabilities through Nostr-native callbacks

  3. Flexible sizing for different use cases (stories, cards, whiteboard elements, …​)

  4. Template variables using Mustache-like syntax with Nostr extensions (Nostache)

Specification

Frame Event

A Hyperframe is created using a kind:1234 event with the following structure:

{
  "kind": 1234,
  "content": "<HTML template with Nostache syntax and embedded styles>",
  "tags": [
    ["dim", "<width>x<height>"],
    ["type", "<frame-type>"],
    ["preview", "<image url>"]
  ]
}

Tag definitions:

  • dim - Required frame dimensions in pixels (e.g. "360x640" for stories)

  • type - Frame type identifier (e.g. "story", "card", "whiteboard-element")

  • preview - Optional preview image URL for clients and/or use cases where rendering the frame doesn’t make sense

Nostache Template Syntax:

The content field uses an extended version of Mustache JS (Wikistr, Wikifreedia) syntax that includes Nostr-specific callbacks. These callbacks follow the NIP-07 (Wikistr, Wikifreedia) window.nostr pattern, where the hosting client acts as the signer/handler for the frame:

Standard Mustache tags:

  • {{variable}} - Variable substitution

  • {{#section}}…​{{/section}} - Section blocks

  • {{^section}}…​{{/section}} - Inverted sections

  • {{!comment}} - Comments

  • {{>partial}} - Partials

Nostr-specific callbacks (Nostache):

  • {{#nostr.getPublicKey}}…​{{/nostr.getPublicKey}} - Get the user’s public key

  • {{#nostr.signEvent}}…​{{/nostr.signEvent}} - Request event signing

  • {{#nostr.zap}}…​{{/nostr.zap}} - Request zap payment (NIP-57 (Wikistr, Wikifreedia))

  • {{#nostr.navigate}}…​{{/nostr.navigate}} - Navigate to another frame or position

Navigation can be done using

  • Event ID: {{#nostr.navigate}}event:{{event_id}}{{/nostr.navigate}}

  • Other navigation methods TBD

Example: Story Frame

{
  "kind": 1234,
  "content": `
    <style>
      .story {
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
      }
      .story img {
        width: 100%;
        height: auto;
      }
      .cta {
        padding: 16px;
        text-align: center;
      }
      .zap-button {
        background: #ff9500;
        color: white;
        border: none;
        padding: 8px 16px;
        border-radius: 8px;
      }
    </style>

    <div class="story">
      <img src="{{image_url}}" alt="{{alt_text}}">
      <div class="cta">
        {{#nostr.zap}}
          <button class="zap-button" data-amount="{{amount}}">
            Zap {{amount}} sats!
          </button>
        {{/nostr.zap}}
      </div>
    </div>
  `,
  "tags": [
    ["dim", "360x640"],
    ["type", "story"],
    ["preview", "https://example.com/story-preview.jpg"]
  ]
}

Example: Simple Whiteboard Note

{
  "kind": 1234,
  "content": `
    <style>
      .note {
        width: 100%;
        height: 100%;
        font-family: Inter, system-ui;
      }
      .note-header {
        font-size: 16px;
        font-weight: 600;
        margin-bottom: 8px;
      }
      .note-content {
        font-size: 14px;
        white-space: pre-wrap;
      }
    </style>

    <div class="note">
      <div class="note-header">{{title}}</div>
      <div class="note-content">{{content}}</div>
    </div>
  `,
  "tags": [
    ["dim", "200x200"],
    ["type", "whiteboard-element"],
    ["pos", "100", "150"]
  ]
}

Implementation Notes

  1. Clients MUST render Hyperframes exactly as specified by the HTML/CSS, maintaining the given dimensions

  2. Clients MUST sandbox Hyperframe content to prevent malicious code execution (NIP-94 (Wikistr, Wikifreedia))

  3. Clients SHOULD implement the standard Nostache callbacks following NIP-07 (Wikistr, Wikifreedia) patterns

When a callback is triggered:

  • The client acts as a NIP-07 (Wikistr, Wikifreedia) provider for the frame

  • All callbacks are async and return promises

  • Callbacks should follow the same security model as NIP-07 (Wikistr, Wikifreedia)

  • User confirmation is required for sensitive operations

Security Considerations

  1. Clients MUST sanitize HTML content to prevent XSS attacks

  2. All callbacks MUST require user confirmation before execution

  3. Frame dimensions MUST be respected to prevent layout manipulation

  4. CSS MUST be scoped to the frame to prevent style leakage

  5. Clients MUST implement resource limits (CPU, memory, network)

  6. Frames MUST NOT have access to the parent window’s DOM

  7. Callbacks MUST follow the same security model as NIP-07 (Wikistr, Wikifreedia)

Benefits

  1. Consistent rendering across all clients

  2. Native Nostr interactivity through standardized NIP-07 (Wikistr, Wikifreedia) style callbacks

  3. Flexible sizing for different use cases

  4. Reusable templates with variable substitution

  5. Secure sandboxed execution

  6. Perfect for stories, cards, interactive posts, and whiteboard elements

Client Recommendations

  1. Implement a robust HTML/CSS sanitizer

  2. Provide clear UI indicators for interactive elements

  3. Cache commonly used templates

  4. Support template sharing between frames

  5. Implement graceful fallbacks for unsupported features

  6. Follow NIP-07 (Wikistr, Wikifreedia) security best practices for callback handling

Author Public Key
npub149p5act9a5qm9p47elp8w8h3wpwn2d7s2xecw2ygnrxqp4wgsklq9g722q