Oddbean new post about | logout
 I will check tomorrow, but I only got the nip04. 

Also, you should ask for the nprofile in the login page. In that way, you will have the relay info to get the DM relays and send the DMs. :)  
 Is the code open somewhere?  
 😅 the code that does the sending is a complete mess and has a non-public info in it. I’ll try to clean it up and open the source for it. Need to parameterize some of the non-public (but not a high value secret or anything) info. 
 t-y Fishcake 
 This took me way longer than I would have liked, but here is the working Typescript code that makes those god damn GW DMs.

// Create DM event
export async function creatNIP17DMEvent(
  fromSk: string,
  toNpub: string,
  dm: string[],
): Promise<Array<Event>> {
  const skBuff = hexToBytes(fromSk)
  const events: Array<Event> = []

  while (dm.length > 0) {
    const msg = dm.shift()
    if (msg)
      events.push(createNip17Wraps(msg, toNpub, skBuff))
  }

  return events
}

function getNow(): number {
  return Math.floor(Date.now() / 1000)
}

function getRandomNow(): number {
  return getNow() - Math.floor(Math.random() * 172800)
}

function nip44Encrypt(rumor: Rumor, privateKey: Uint8Array, publicKey: string): string {
  const key = nip44.v2.utils.getConversationKey(privateKey, publicKey)
  return nip44.v2.encrypt(JSON.stringify(rumor), key)
}

function createWrap(event: Event, recipientPublicKey: string): Event {
  // Get random secret key to wrap the event
  const randomSK = generateSecretKey()
  return finalizeEvent({
    kind: 1059, // Gift wrapped event
    content: nip44Encrypt(event, randomSK, recipientPublicKey),
    created_at: getRandomNow(),
    tags: [["p", recipientPublicKey]]
  }, randomSK) as Event
}

function createSeal(event: Rumor, privateKey: Uint8Array, recipientPublicKey: string): Event {
  return finalizeEvent({
    kind: 13, // Seal
    content: nip44Encrypt(event, privateKey, recipientPublicKey),
    created_at: getRandomNow(),
    tags: []
  }, privateKey)
}

function createRumor(message: string, senderPubkey: string, receiverPubkey: string): Rumor {
  const rumor = {
    kind: 14, // As per specs, should be Kind 14 event
    content: message,
    pubkey: senderPubkey,
    created_at: getNow(),
    tags: [["p", receiverPubkey]],
  } as Rumor
  rumor.id = getEventHash(rumor)
  return rumor
}

function createNip17Wraps(message: string, recipientPublicKey: string, senderSk: Uint8Array): Event {
  const senderPubkey = getPublicKey(senderSk)
  return createWrap(
    createSeal(
      createRumor(message, senderPubkey, recipientPublicKey),
      senderSk, recipientPublicKey),
    recipientPublicKey)
} 
 One fix for types:

function nip44Encrypt(rumor: Rumor | VerifiedEvent, privateKey: Uint8Array, publicKey: string): string {
  const key = nip44.v2.utils.getConversationKey(privateKey, publicKey)
  return nip44.v2.encrypt(JSON.stringify(rumor), key)
}


It does not have any impact, just cosmetics 
 It works! :)  
 Yeah! I do suggest to make a clear documentation about how it should be done, I will contribute if needed. It is super confusing now and no single NIP to look at 🫂😭 
 Maybe send a PR with some of this code to nostr-tools?  
 Good idea! 🫂 
 I should add nip40 expiration to the wrap too I think, if the event is OTP 
 Great idea  
 I had some other issues, and didn’t have the messages actually sent anywhere, now they are but none of the clients see them only 04. Hence my point, if it is complex and close to impossible to implement even with examples, it will not be used or will be misused or misimplemented 😭😭😭