attn-protocol

ATTN SDK

TypeScript SDK for creating and publishing ATTN Protocol events on Nostr.

Overview

The ATTN SDK provides type-safe event creation and publishing for the ATTN Protocol. It complements the attn framework (which receives/processes events) by providing event creation and publishing capabilities.

The SDK depends on @attn/ts-core for shared constants and type definitions. Event kind constants are available from the core package and are used internally by all event builders to ensure consistency across the ATTN Protocol ecosystem.

Installation

npm install @attn/ts-sdk

Quick Start

import { AttnSdk } from "@attn/ts-sdk";
import { ATTN_EVENT_KINDS } from "@attn/ts-core";

// Initialize SDK with private key (hex or nsec format)
const sdk = new AttnSdk({
  private_key: "your_private_key_here", // hex or nsec
});

// Note: Block events (kind 38808) are now published by City Protocol (@city/clock)
// ATTN SDK provides a deprecated create_block method for backwards compatibility
// For new implementations, use @city/clock for block event publishing

// Create and publish a PROMOTION event that matches ATTN-01
const promotion_event = sdk.create_promotion({
  promotion_id: "promotion-001", // d tag + content identifier
  duration: 30_000, // milliseconds
  bid: 5_000, // sats for the full duration
  event_id: "video_event_id_here",
  description: "Watch my amazing content",
  call_to_action: "Watch Now",
  call_to_action_url: "https://example.com/watch",
  marketplace_coordinate: "38188:marketplace_pubkey:marketplace_001",
  video_coordinate: "34236:video_author_pubkey:video_d_tag",
  billboard_coordinate: "38288:billboard_pubkey:billboard_001",
  marketplace_pubkey: "marketplace_pubkey_hex",
  promotion_pubkey: sdk.get_public_key(),
  relays: ["wss://relay.attnprotocol.org"],
  kind: 34236, // promoted kind (k tag)
  url: "https://example.com/promotion",
  marketplace_id: "marketplace_001",
  block_height: 862626, // sets the t tag per ATTN-01
});

// Publish to a single relay
await sdk.publish(promotion_event, "wss://relay.attnprotocol.org");

// Or publish to multiple relays
await sdk.publish_to_multiple(promotion_event, [
  "wss://relay.attnprotocol.org",
  "wss://relay.attnprotocol.org",
]);

Every builder implements the schemas and tag layout defined in @attn-protocol/packages/protocol/docs/ATTN-01.md. Content fields live in the JSON payload, while routing/indexing values (identifiers, block height, coordinates) ride inside Nostr tags. Always pass the current Bitcoin block_height so the SDK can emit the t tag that powers block-synchronized filtering.

Note: All event builders use constants from @attn/ts-core internally. You can import ATTN_EVENT_KINDS to reference event kinds in your code:

import { ATTN_EVENT_KINDS } from "@attn/ts-core";

// Check event kind
if (event.kind === ATTN_EVENT_KINDS.PROMOTION) {
  // Handle promotion event
}

Type Reference

Event Parameter Types

Builder Method Parameter Type Return Type
create_block() BlockEventParams Event
create_marketplace() MarketplaceEventParams Event
create_billboard() BillboardEventParams Event
create_promotion() PromotionEventParams Event
create_attention() AttentionEventParams Event
create_match() MatchEventParams Event
create_billboard_confirmation_event() BillboardConfirmationEventParams Event
create_attention_confirmation_event() AttentionConfirmationEventParams Event
create_marketplace_confirmation_event() MarketplaceConfirmationEventParams Event

Common Field Types

Field Type Example Notes
block_height number 862626 Bitcoin block height
duration number 30000 Milliseconds
bid / ask number 5000 Total satoshis for duration
coordinate string "38188:pubkey:id" Format: <kind>:<pubkey>:<identifier>
promotion_id string "promotion-001" Unique identifier
marketplace_id string "marketplace_001" Unique identifier
billboard_id string "billboard_001" Unique identifier
attention_id string "attention_001" Unique identifier
match_id string "match_001" Unique identifier
kind_list number[] [34236, 1, 30023] Array of event kind numbers
relay_list string[] ["wss://relay.example.com"] Array of relay URLs

Event Types

Each subsection restates the ATTN-01 content + tag requirements so builders stay in lockstep with the block-synchronized snapshot model. When the SDK does not yet expose a specific field/tag (for example, relay_list inside the PROMOTION content), treat that as a TODO before publishing—extend the helper or compose the JSON manually so every required field lands on-chain of record.

BLOCK Event (kind 38808 - City Protocol)

Note: Block events are now published by City Protocol (kind 38808). The ATTN SDK’s create_block method is deprecated and kept only for backwards compatibility. For new implementations, use @city/clock from the City Protocol.

City Protocol block events (CITY-01) include:

For creating block events, see the City Protocol documentation.

PROMOTION Event (kind ATTN_EVENT_KINDS.PROMOTION / 38388)

ATTN-01 content requirements:

ATTN-01 tag requirements:

const promotion_event = sdk.create_promotion({
  promotion_id: "promotion-001",
  duration: 30_000,
  bid: 5_000,
  event_id: "video_event_id_here",
  description: "Watch my amazing content",
  call_to_action: "Watch Now",
  call_to_action_url: "https://example.com/watch",
  marketplace_coordinate: "38188:marketplace_pubkey:marketplace_001",
  video_coordinate: "34236:video_author_pubkey:video_d_tag",
  billboard_coordinate: "38288:billboard_pubkey:billboard_001",
  marketplace_pubkey: "marketplace_pubkey_hex",
  promotion_pubkey: sdk.get_public_key(),
  relays: ["wss://relay.attnprotocol.org"],
  kind: 34236,
  url: "https://example.com/promotion",
  marketplace_id: "marketplace_001",
  block_height: 862626,
});

ATTENTION Event (kind ATTN_EVENT_KINDS.ATTENTION / 38488)

ATTN-01 content requirements:

ATTN-01 tag requirements:

const attention_event = sdk.create_attention({
  attention_id: "attention-001",
  ask: 3_000,
  min_duration: 15_000,
  max_duration: 60_000,
  kind_list: [34236, 1, 30023],
  relay_list: ["wss://relay.attnprotocol.org"],
  marketplace_coordinate: "38188:marketplace_pubkey:marketplace_001",
  blocked_promotions_coordinate: "30000:attention_pubkey:org.attnprotocol:promotion:blocked",
  blocked_promoters_coordinate: "30000:attention_pubkey:org.attnprotocol:promoter:blocked",
  blocked_promotions_id: "org.attnprotocol:promotion:blocked",
  blocked_promoters_id: "org.attnprotocol:promoter:blocked",
  attention_pubkey: sdk.get_public_key(),
  marketplace_pubkey: "marketplace_pubkey_hex",
  relays: ["wss://relay.attnprotocol.org"],
  kinds: [34236, 1, 30023],
  marketplace_id: "marketplace_001",
  block_height: 862626,
});

MATCH Event (kind ATTN_EVENT_KINDS.MATCH / 38888)

ATTN-01 content requirements:

ATTN-01 tag requirements:

const match_event = sdk.create_match({
  match_id: "match-001",
  marketplace_coordinate: "38188:marketplace_pubkey:marketplace_001",
  billboard_coordinate: "38288:billboard_pubkey:billboard_001",
  promotion_coordinate: "38388:promotion_pubkey:promotion_001",
  attention_coordinate: "38488:attention_pubkey:attention_001",
  marketplace_pubkey: "marketplace_pubkey_hex",
  promotion_pubkey: "promotion_pubkey_hex",
  attention_pubkey: "attention_pubkey_hex",
  billboard_pubkey: "billboard_pubkey_hex",
  marketplace_id: "marketplace_001",
  billboard_id: "billboard_001",
  promotion_id: "promotion_001",
  attention_id: "attention_001",
  ask: 3_000,
  bid: 5_000,
  duration: 30_000,
  kind_list: [34236],
  relay_list: ["wss://relay.attnprotocol.org"],
  relays: ["wss://relay.attnprotocol.org"],
  block_height: 862626,
});

MARKETPLACE Event (kind ATTN_EVENT_KINDS.MARKETPLACE / 38188)

ATTN-01 content requirements:

ATTN-01 tag requirements:

const marketplace_event = sdk.create_marketplace({
  marketplace_id: "marketplace-001",
  name: "Example Marketplace",
  description: "Decentralized attention marketplace",
  kind_list: [34236, 1, 30023],
  relay_list: ["wss://relay.attnprotocol.org"],
  admin_pubkey: sdk.get_public_key(),
  marketplace_pubkey: sdk.get_public_key(),
  image: "https://example.com/image.png",
  url: "https://marketplace.example.com",
  website_url: "https://marketplace.example.com",
  admin_email: "admin@example.com",
  min_duration: 15_000,
  max_duration: 60_000,
  block_height: 862626,
});

BILLBOARD Event (kind ATTN_EVENT_KINDS.BILLBOARD / 38288)

ATTN-01 content requirements:

ATTN-01 tag requirements:

const billboard_event = sdk.create_billboard({
  billboard_id: "billboard-001",
  name: "My Billboard",
  description: "A great billboard for promotions",
  marketplace_coordinate: "38188:marketplace_pubkey:marketplace_001",
  billboard_pubkey: sdk.get_public_key(),
  marketplace_pubkey: "marketplace_pubkey_hex",
  relays: ["wss://relay.attnprotocol.org"],
  kind: 34236,
  url: "https://billboard.example.com",
  marketplace_id: "marketplace_001",
  block_height: 862626,
});

BILLBOARD_CONFIRMATION Event (kind ATTN_EVENT_KINDS.BILLBOARD_CONFIRMATION / 38588)

ATTN-01 content requirements:

ATTN-01 tag requirements:

import { create_billboard_confirmation_event } from "@attn/ts-sdk";
import { nip19 } from "nostr-tools";

const decoded = nip19.decode("nsec1...");
const private_key = decoded.data as Uint8Array;

const billboard_confirmation = create_billboard_confirmation_event(
  private_key,
  {
    block: 862626,
    price: 2_000,
    marketplace_ref: "marketplace_event_id",
    promotion_ref: "promotion_event_id",
    attention_ref: "attention_event_id",
    match_ref: "match_event_id",
    marketplace_coordinate: "38188:marketplace_pubkey:marketplace_001",
    promotion_coordinate: "38388:promotion_pubkey:promotion_001",
    attention_coordinate: "38488:attention_pubkey:attention_001",
    match_coordinate: "38888:marketplace_pubkey:match_001",
    marketplace_pubkey: "marketplace_pubkey_hex",
    promotion_pubkey: "promotion_pubkey_hex",
    attention_pubkey: "attention_pubkey_hex",
    billboard_pubkey: "billboard_pubkey_hex",
    marketplace_id: "marketplace_001",
    promotion_id: "promotion_001",
    attention_id: "attention_001",
    match_id: "match_001",
    relays: ["wss://relay.attnprotocol.org"],
    url: "https://billboard.example.com/confirmation",
  }
);

ATTENTION_CONFIRMATION Event (kind ATTN_EVENT_KINDS.ATTENTION_CONFIRMATION / 38688)

ATTN-01 content requirements:

ATTN-01 tag requirements:

import { create_attention_confirmation_event } from "@attn/ts-sdk";
import { nip19 } from "nostr-tools";

const decoded = nip19.decode("nsec1...");
const private_key = decoded.data as Uint8Array;

const attention_confirmation = create_attention_confirmation_event(
  private_key,
  {
    block: 862626,
    price: 3_000,
    sats_delivered: 3_000,
    proof_payload: "viewer_proof_data",
    marketplace_ref: "marketplace_event_id",
    promotion_ref: "promotion_event_id",
    attention_ref: "attention_event_id",
    match_ref: "match_event_id",
    marketplace_coordinate: "38188:marketplace_pubkey:marketplace_001",
    promotion_coordinate: "38388:promotion_pubkey:promotion_001",
    attention_coordinate: "38488:attention_pubkey:attention_001",
    match_coordinate: "38888:marketplace_pubkey:match_001",
    marketplace_pubkey: "marketplace_pubkey_hex",
    promotion_pubkey: "promotion_pubkey_hex",
    attention_pubkey: "attention_pubkey_hex",
    billboard_pubkey: "billboard_pubkey_hex",
    marketplace_id: "marketplace_001",
    promotion_id: "promotion_001",
    attention_id: "attention_001",
    match_id: "match_001",
    relays: ["wss://relay.attnprotocol.org"],
    url: "https://viewer.example.com/confirmation",
  }
);

MARKETPLACE_CONFIRMATION Event (kind ATTN_EVENT_KINDS.MARKETPLACE_CONFIRMATION / 38788)

ATTN-01 content requirements:

ATTN-01 tag requirements:

import { create_marketplace_confirmation_event } from "@attn/ts-sdk";
import { nip19 } from "nostr-tools";

const decoded = nip19.decode("nsec1...");
const private_key = decoded.data as Uint8Array;

const marketplace_confirmation = create_marketplace_confirmation_event(
  private_key,
  {
    block: 862626,
    duration: 30_000,
    ask: 3_000,
    bid: 5_000,
    price: 5_000,
    sats_settled: 5_000,
    payout_breakdown: {
      viewer: 3_000,
      billboard: 2_000,
    },
    marketplace_ref: "marketplace_event_id",
    promotion_ref: "promotion_event_id",
    attention_ref: "attention_event_id",
    match_ref: "match_event_id",
    billboard_confirmation_ref: "billboard_confirmation_event_id",
    attention_confirmation_ref: "attention_confirmation_event_id",
    marketplace_coordinate: "38188:marketplace_pubkey:marketplace_001",
    promotion_coordinate: "38388:promotion_pubkey:promotion_001",
    attention_coordinate: "38488:attention_pubkey:attention_001",
    match_coordinate: "38888:marketplace_pubkey:match_001",
    marketplace_pubkey: "marketplace_pubkey_hex",
    promotion_pubkey: "promotion_pubkey_hex",
    attention_pubkey: "attention_pubkey_hex",
    billboard_pubkey: "billboard_pubkey_hex",
    marketplace_id: "marketplace_001",
    promotion_id: "promotion_001",
    attention_id: "attention_001",
    match_id: "match_001",
    relays: ["wss://relay.attnprotocol.org"],
    url: "https://marketplace.example.com/confirmation",
  }
);

Note: Confirmation event builders require a Uint8Array private key. You can decode from hex or nsec format as shown above.

Publishing Events

Single Relay

const result = await sdk.publish(event, "wss://relay.attnprotocol.org");
console.log(result.success); // true or false
console.log(result.event_id); // Event ID
console.log(result.error); // Error message if failed

Multiple Relays

const results = await sdk.publish_to_multiple(event, [
  "wss://relay.attnprotocol.org",
  "wss://relay.attnprotocol.org",
]);

console.log(results.success_count); // Number of successful publishes
console.log(results.failure_count); // Number of failed publishes
console.log(results.results); // Array of individual results

Private Key Formats

The SDK supports both hex and nsec (nip19) private key formats:

// Hex format (64 hex characters)
const sdk1 = new AttnSdk({
  private_key: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
});

// Nsec format (nip19 encoded)
const sdk2 = new AttnSdk({
  private_key: "nsec1...",
});

Configuration Validation

The SDK validates configuration in the constructor:

All validation happens at construction time, ensuring the SDK instance is always in a valid state.

Examples

Basic Usage

// Import SDK for creating events
import { AttnSdk } from "@attn/ts-sdk";
// Import framework for receiving/processing events
import { Attn } from "@attn/ts-framework";

// Initialize SDK with private key (hex or nsec format)
const sdk = new AttnSdk({
  private_key: "your_private_key_here", // hex or nsec
});

// Note: Block events (kind 38808) are now published by City Protocol (@city/clock)
// ATTN Protocol services subscribe to City Protocol block events for timing

// Initialize framework for receiving events
const attn = new Attn({
  relays: ["wss://relay.attnprotocol.org"],
  private_key: private_key_uint8array, // Uint8Array for NIP-42 auth
  clock_pubkeys: [clock_pubkey_hex], // Trusted City Protocol clock services
});

// Register hook handlers
attn.on_new_block(async (context) => {
  console.log(`New block: ${context.block_height} (hash: ${context.block_hash})`);
  // Process block-synchronized tasks here
});

attn.on_new_promotion(async (context) => {
  console.log("New promotion received:", context.event);
  // Handle promotion event
});

// Connect to relays
try {
  await attn.connect();
  console.log("Connected to relays");
} catch (error) {
  console.error("Failed to connect:", error);
}

Pattern 1: Creating a Complete Promotion Flow

import { AttnSdk } from "@attn/ts-sdk";

const sdk = new AttnSdk({ private_key: "your_private_key" });

// 1. Create marketplace
const marketplace = sdk.create_marketplace({
  marketplace_id: "marketplace-001",
  name: "Example Marketplace",
  description: "Decentralized attention marketplace",
  kind_list: [34236],
  relay_list: ["wss://relay.attnprotocol.org"],
  admin_pubkey: sdk.get_public_key(),
  marketplace_pubkey: sdk.get_public_key(),
  block_height: 862626,
});

// 2. Create billboard
const billboard = sdk.create_billboard({
  billboard_id: "billboard-001",
  name: "My Billboard",
  marketplace_coordinate: `38188:${sdk.get_public_key()}:marketplace-001`,
  billboard_pubkey: sdk.get_public_key(),
  marketplace_pubkey: sdk.get_public_key(),
  relays: ["wss://relay.attnprotocol.org"],
  kind: 34236,
  url: "https://billboard.example.com",
  marketplace_id: "marketplace-001",
  block_height: 862626,
});

// 3. Create promotion
const promotion = sdk.create_promotion({
  promotion_id: "promotion-001",
  duration: 30_000,
  bid: 5_000,
  event_id: "video_event_id_here",
  description: "Watch my amazing content",
  call_to_action: "Watch Now",
  call_to_action_url: "https://example.com/watch",
  marketplace_coordinate: `38188:${sdk.get_public_key()}:marketplace-001`,
  billboard_coordinate: `38288:${sdk.get_public_key()}:billboard-001`,
  video_coordinate: "34236:video_author_pubkey:video_d_tag",
  marketplace_pubkey: sdk.get_public_key(),
  promotion_pubkey: sdk.get_public_key(),
  relays: ["wss://relay.attnprotocol.org"],
  kind: 34236,
  url: "https://example.com/promotion",
  marketplace_id: "marketplace-001",
  block_height: 862626,
});

// 4. Publish to relays
await sdk.publish_to_multiple(promotion, ["wss://relay.attnprotocol.org"]);

Pattern 2: Matching Logic

// Matching criteria (all must be true):
// 1. Economic: promotion.bid >= attention.ask
// 2. Duration: attention.min_duration <= promotion.duration <= attention.max_duration
// 3. Kind: promotion.kind in attention.kind_list
// 4. Block list: promotion not in attention.blocked_promotions AND promoter not in attention.blocked_promoters

function is_match_valid(
  promotion: PromotionEvent,
  attention: AttentionEvent,
  blocked_promotions: string[],
  blocked_promoters: string[]
): boolean {
  // Economic compatibility
  if (promotion.bid < attention.ask) {
    return false;
  }

  // Duration compatibility
  if (
    promotion.duration < attention.min_duration ||
    promotion.duration > attention.max_duration
  ) {
    return false;
  }

  // Kind compatibility
  if (!attention.kind_list.includes(promotion.kind)) {
    return false;
  }

  // Block list checks
  const promotion_coordinate = `38388:${promotion.promotion_pubkey}:${promotion.promotion_id}`;
  if (blocked_promotions.includes(promotion_coordinate)) {
    return false;
  }

  if (blocked_promoters.includes(promotion.promotion_pubkey)) {
    return false;
  }

  return true;
}

// Create MATCH event when valid
if (is_match_valid(promotion, attention, blocked_promotions, blocked_promoters)) {
  const match = sdk.create_match({
    match_id: "match-001",
    marketplace_coordinate: `38188:${marketplace_pubkey}:${marketplace_id}`,
    billboard_coordinate: `38288:${billboard_pubkey}:${billboard_id}`,
    promotion_coordinate: `38388:${promotion_pubkey}:${promotion_id}`,
    attention_coordinate: `38488:${attention_pubkey}:${attention_id}`,
    marketplace_pubkey,
    promotion_pubkey,
    attention_pubkey,
    billboard_pubkey,
    marketplace_id,
    billboard_id,
    promotion_id,
    attention_id,
    ask: attention.ask,
    bid: promotion.bid,
    duration: promotion.duration,
    kind_list: attention.kind_list,
    relay_list: attention.relay_list,
    relays: attention.relay_list,
    block_height: current_block_height,
  });
}

Validation Utilities

The SDK includes validation utilities for event validation:

import {
  validate_block_height,
  validate_json_content,
  validate_pubkey,
} from "@attn/ts-sdk";

const result = validate_block_height(event);
if (!result.valid) {
  console.error(result.message);
}

Type Safety

All event parameters are fully typed with TypeScript interfaces:

import type {
  PromotionEventParams,
  AttentionEventParams,
  MatchEventParams,
} from "@attn/ts-sdk";

Bitcoin Block Height Support

All events support Bitcoin block height for block-synchronized operations:

const event = sdk.create_promotion({
  // ... other params
  block_height: 862626, // Current Bitcoin block height
});

Error Handling

All publishing methods return detailed error information:

const result = await sdk.publish(event, relay_url);
if (!result.success) {
  console.error(`Failed to publish: ${result.error}`);
}

License

MIT