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)
}