Skip to content

Commit

Permalink
refactor(sdk): remove rust-nostr-sdk deps (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
RetricSu authored Jul 11, 2024
1 parent 26ad0e5 commit 04b56c7
Show file tree
Hide file tree
Showing 16 changed files with 182 additions and 70 deletions.
6 changes: 6 additions & 0 deletions packages/demo/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ module.exports = {
plugins: ["@typescript-eslint", "import"],
rules: {
"react/prop-types": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
},
],
},
parser: "@typescript-eslint/parser",
settings: {
Expand Down
6 changes: 5 additions & 1 deletion packages/demo/app/conmponents/asset-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ export const AssetBox: React.FC<AssetBoxProp> = ({ cell, setResult }) => {
};

useEffect(() => {
getBindingEvent(cell).then((event) => setEvent(event));
getBindingEvent(cell).then((event) => {
if (event) {
setEvent(Event.fromJson(JSON.stringify(event)));
}
});
}, [cell]);

return (
Expand Down
3 changes: 2 additions & 1 deletion packages/demo/app/conmponents/connect-nostr.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import { CKBSigner, SingerContext } from "~/context/signer";
import { capacityOf } from "~/lib/ckb.client";
import { readEnvNetwork } from "offckb.config";
import ExpandableDiv from "./expandable";
import { EventToSign, SignedEvent, joyIdNip07Signer } from "@nostr-binding/sdk";
import { EventToSign, SignedEvent } from "@nostr-binding/sdk";
import { sdk } from "~/lib/sdk.client";
import { joyIdNip07Signer } from "~/lib/nip07.client";

export function ConnectNostr() {
const [nostrPubkey, setNostrPubkey] = useState<string>();
Expand Down
6 changes: 4 additions & 2 deletions packages/demo/app/conmponents/mint-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { blockchain } from "@ckb-lumos/base";
import { ReactNode, useContext } from "react";
import { SingerContext } from "~/context/signer";
import offCKB from "offckb.config";
import { Event } from "@rust-nostr/nostr-sdk";
import { Event, UnsignedEvent } from "@rust-nostr/nostr-sdk";
import { jsonStringToBytes } from "@nostr-binding/sdk";
import { buildMintTransaction } from "~/lib/ckb.client";
import { createTransactionFromSkeleton } from "@ckb-lumos/lumos/helpers";
Expand All @@ -27,7 +27,9 @@ export function MintButton({ setResult, setAssetEvent }: MintButtonProp) {
let txSkeleton = result.txSkeleton;
const mintEvent = result.mintEvent;

const signedMintEvent = await nostrSigner.signEvent(mintEvent);
const signedMintEvent = await nostrSigner.signEvent(
UnsignedEvent.fromJson(JSON.stringify(mintEvent)),
);
setAssetEvent(signedMintEvent);

const mintEventWitness = bytes.hexify(
Expand Down
2 changes: 1 addition & 1 deletion packages/demo/app/lib/ckb.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export async function buildMintTransaction(

const lock = sdk.lock.buildScript("0x" + receiverNostrPublicKey.toHex());
const bindingCell = sdk.binding.buildBindingCell(
mintEvent.id!.toHex(),
mintEvent.id!,
globalUniqueId,
lock,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { nostr } from '@joyid/nostr';
import { Event, Nip07Signer, PublicKey, UnsignedEvent } from '@rust-nostr/nostr-sdk';
import { nostr } from "@joyid/nostr";
import {
Event,
Nip07Signer,
PublicKey,
UnsignedEvent,
} from "@rust-nostr/nostr-sdk";

export class joyIdNip07Signer extends Nip07Signer {
constructor() {
Expand All @@ -9,7 +14,7 @@ export class joyIdNip07Signer extends Nip07Signer {
}

free(): void {
throw new Error('Method not implemented.');
throw new Error("Method not implemented.");
}

async getPublicKey(): Promise<PublicKey> {
Expand All @@ -23,15 +28,15 @@ export class joyIdNip07Signer extends Nip07Signer {
}

nip04Encrypt(_publicKey: PublicKey, _plaintext: string): Promise<string> {
throw new Error('Method not implemented.');
throw new Error("Method not implemented.");
}
nip04Decrypt(_publicKey: PublicKey, _ciphertext: string): Promise<string> {
throw new Error('Method not implemented.');
throw new Error("Method not implemented.");
}
nip44Encrypt(_publicKey: PublicKey, _plaintext: string): Promise<string> {
throw new Error('Method not implemented.');
throw new Error("Method not implemented.");
}
nip44Decrypt(_publicKey: PublicKey, _ciphertext: string): Promise<string> {
throw new Error('Method not implemented.');
throw new Error("Method not implemented.");
}
}
1 change: 1 addition & 0 deletions packages/sdk/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ module.exports = {
argsIgnorePattern: '^_',
},
],
'@typescript-eslint/no-explicit-any': 'off',
},
};
3 changes: 1 addition & 2 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"@ckb-lumos/base": "^0.23.0",
"@ckb-lumos/codec": "^0.23.0",
"@ckb-lumos/lumos": "^0.23.0",
"@joyid/nostr": "^0.1.5",
"@rust-nostr/nostr-sdk": "^0.32.1"
"@noble/hashes": "^1.4.0"
}
}
32 changes: 12 additions & 20 deletions packages/sdk/src/binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,8 @@ import { bytesToJsonString } from './util';
import { bytes } from '@ckb-lumos/codec';
import { TESTNET_CONFIGS } from './config';
import { ScriptConfig } from '@ckb-lumos/lumos/config';
import { Event, EventId, Tag, PublicKey, Timestamp, UnsignedEvent } from '@rust-nostr/nostr-sdk';
import { TagName } from './tag';

export interface EventToBind {
readonly pubkey: string;
readonly created_at: number;
readonly kind: number;
tags: string[][];
readonly content: string;
}
import { calcEventId, EventToBind, parseSignedEvent } from './event';

export class NostrBinding {
readonly prefix: 'ckt' | 'ckb';
Expand All @@ -34,7 +26,7 @@ export class NostrBinding {
const eventBytes = bytes.bytify(outputType);
const eventJsonString = bytesToJsonString(eventBytes);
try {
return Event.fromJson(eventJsonString);
return parseSignedEvent(eventJsonString);
} catch (error: unknown) {
console.debug(error);
return null;
Expand All @@ -52,19 +44,19 @@ export class NostrBinding {
};
}

finalizeEventToBind(ckbGlobalUniqueId: string, event: EventToBind): UnsignedEvent {
finalizeEventToBind(ckbGlobalUniqueId: string, event: EventToBind) {
const tags = event.tags;
tags.push([TagName.ckbGlobalUniqueId, ckbGlobalUniqueId]);
const eventId = new EventId(
PublicKey.fromHex(event.pubkey),
Timestamp.fromSecs(event.created_at),
event.kind,
tags.map((tag) => Tag.parse(tag)),
event.content,
);
const finalizedEvent = { ...event, ...{ id: eventId.toHex(), tags } };
const eventId = calcEventId({
kind: event.kind,
pubkey: event.pubkey,
created_at: event.created_at,
content: event.content,
tags: tags,
});
const finalizedEvent = { ...event, ...{ id: eventId, tags } };
console.debug('finalizedEventToBind: ', finalizedEvent);
return UnsignedEvent.fromJson(JSON.stringify(finalizedEvent));
return finalizedEvent;
}

buildBindingCell(eventId: HexString, ckbGlobalUniqueId: HexString, lock: Script) {
Expand Down
83 changes: 83 additions & 0 deletions packages/sdk/src/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { sha256 } from '@noble/hashes/sha256';
import { bytesToHex } from '@noble/hashes/utils';

export interface EventToSign {
readonly created_at: number;
readonly kind: number;
readonly tags: string[][];
readonly content: string;
}

export interface EventToBind {
readonly pubkey: string;
readonly created_at: number;
readonly kind: number;
tags: string[][];
readonly content: string;
}

export interface SignedEvent {
readonly id: string;
readonly pubkey: string;
readonly created_at: number;
readonly kind: number;
readonly tags: string[][];
readonly content: string;
readonly sig: string;
}

export type PreFinalizedEvent = Omit<SignedEvent, 'sig' | 'id'>;

export function isValidSignedEvent(obj: any): obj is SignedEvent {
return (
typeof obj === 'object' &&
obj !== null &&
typeof obj.id === 'string' &&
typeof obj.pubkey === 'string' &&
typeof obj.created_at === 'number' &&
typeof obj.kind === 'number' &&
Array.isArray(obj.tags) &&
obj.tags.every((tag: any) => Array.isArray(tag) && tag.every((t: any) => typeof t === 'string')) &&
typeof obj.content === 'string' &&
typeof obj.sig === 'string'
);
}

export function isValidPreFinalizedEvent(obj: any): obj is PreFinalizedEvent {
return (
typeof obj === 'object' &&
obj !== null &&
typeof obj.pubkey === 'string' &&
typeof obj.created_at === 'number' &&
typeof obj.kind === 'number' &&
Array.isArray(obj.tags) &&
obj.tags.every((tag: any) => Array.isArray(tag) && tag.every((t: any) => typeof t === 'string')) &&
typeof obj.content === 'string'
);
}

export function parseSignedEvent(jsonString: string): SignedEvent | null {
try {
const obj = JSON.parse(jsonString);
if (isValidSignedEvent(obj)) {
return obj;
} else {
console.error('Invalid SignedEvent format');
return null;
}
} catch (e) {
console.error('Invalid JSON string', e);
return null;
}
}

export function serializeEvent(event: PreFinalizedEvent): string {
if (!isValidPreFinalizedEvent(event)) throw new Error('invalid event');
return JSON.stringify([0, event.pubkey, event.created_at, event.kind, event.tags, event.content]);
}

export function calcEventId(event: PreFinalizedEvent): string {
const utf8Encoder = new TextEncoder();
const eventHash = sha256(utf8Encoder.encode(serializeEvent(event)));
return bytesToHex(eventHash);
}
9 changes: 1 addition & 8 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
import { loadWasmSync } from '@rust-nostr/nostr-sdk';
import { NostrBinding } from './binding';
import { NostrLock } from './lock';
import { NostrBindingConfig } from './config';

export * from './lock';
export * from './binding';
export * from './config';
export * from './signer';
export * from './tag';
export * from './util';

export function init() {
loadWasmSync();
}
export * from './event';

export class NostrBindingSDK {
binding: NostrBinding;
lock: NostrLock;

constructor(config?: NostrBindingConfig) {
init();

if (config) {
this.binding = new NostrBinding(config.NOSTR_BINDING, config.prefix);
this.lock = new NostrLock(config.NOSTR_LOCK, config.prefix);
Expand Down
26 changes: 5 additions & 21 deletions packages/sdk/src/lock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,13 @@ import { CellDep, HexString, helpers, utils, WitnessArgs, Script, RPC } from '@c
import { ScriptConfig } from '@ckb-lumos/lumos/config';
import { TESTNET_CONFIGS } from './config';
import { TagName } from './tag';
import { bytesToJsonString, jsonStringToBytes } from './util';
import { bytesToJsonString, getTimestampNowSecs, jsonStringToBytes } from './util';
import { blockchain, Transaction } from '@ckb-lumos/base';
import { bytes, number } from '@ckb-lumos/codec';
import { Event, Timestamp } from '@rust-nostr/nostr-sdk';
import { EventToSign, parseSignedEvent, SignedEvent } from './event';

const { Uint64 } = number;

export interface EventToSign {
readonly created_at: number;
readonly kind: number;
readonly tags: string[][];
readonly content: string;
}

export interface SignedEvent {
readonly id: string;
readonly pubkey: string;
readonly created_at: number;
readonly kind: number;
readonly tags: string[][];
readonly content: string;
}

export class NostrLock {
readonly kind = 23334;
readonly content =
Expand Down Expand Up @@ -56,7 +40,7 @@ export class NostrLock {
const eventBytes = bytes.bytify(lock);
const eventJsonString = bytesToJsonString(eventBytes);
try {
return Event.fromJson(eventJsonString);
return parseSignedEvent(eventJsonString);
} catch (error: unknown) {
console.debug(error);
return null;
Expand Down Expand Up @@ -352,7 +336,7 @@ export class NostrLock {
id: '00'.repeat(32),
pubkey: '00'.repeat(32),
tags,
created_at: Timestamp.now().asSecs(),
created_at: getTimestampNowSecs(),
kind: this.kind,
content: this.content,
sig: '00'.repeat(64),
Expand All @@ -364,7 +348,7 @@ export class NostrLock {
buildUnlockEvent(ckbSigHashAll: HexString): EventToSign {
const unlockEvent: EventToSign = {
tags: [[TagName.ckbSigHashAll, ckbSigHashAll.slice(2)]],
created_at: Timestamp.now().asSecs(),
created_at: getTimestampNowSecs(),
kind: this.kind,
content: this.content,
};
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ export function bytesToJsonString(bytes: Uint8Array): string {
// Convert the buffer to a string
return buffer.toString('utf-8');
}

export function getTimestampNowSecs() {
return Math.floor(Date.now() / 1000);
}
35 changes: 35 additions & 0 deletions packages/sdk/tests/event.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { calcEventId } from '../src';

describe('Nostr Event', () => {
test('Build EventId', () => {
const expectEventId = '5b6ea23af84de6241bbb350e56a1efd003afa5bf224600f233cf013b9c821bb5';
const event = {
pubkey: '84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240',
created_at: 1716936887,
kind: 1,
tags: [
['p', '84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240'],
['p', '6389be6491e7b693e9f368ece88fcd145f07c068d2c1bbae4247b9b5ef439d32'],
['e', 'ce2f7fcc291eda1586b8975328d928451de0178927ffe3b2f2eac292136a5a3e', 'wss://junxingwang.org', 'root'],
],
content: 'The Industrial Revolution and its consequences have been a disaster for the human race. ',
};
const eventId = calcEventId(event);
expect(eventId).toBe(expectEventId);
});

test('Build EventId Chinese', () => {
const expectEventId = '08013c58d11f02a4c4e75cb7386ce0ad25c0ff18a7539de6ab666fa6aa7c300c';
const event = {
content:
'这么问是因为我觉得整合nostr数据的LLM可以作为nostr的客户端,不用传统的社交媒体UI,使用Chat的UI。\n\n可以直接问:\n“简要报告今天关注的人发布了什么内容”\n“把今天最热门讨论内容摘要给我”\n“我想了解目前NIP XX讨论的最新进度”\n“用克林贡语发一篇帖子,同时通知X某某,内容是…”\n“发帖春节快乐,zap前十个祝福语回复各2100sats”\n\n也可以实现为其他平台比如Telegram的Bot,关联nsec后方便跨平台使用。\n\nnostr一直在讨论内容算法,LLM相比单薄的内容算法好太多了。 nostr:note14vremq6ugjt6p0rh0eh52ulykvphmugrc6zsvg57htsggjup4wqskzqv9g',
created_at: 1717500090,
kind: 1,
pubkey: 'f0c864cf573de171053bef4df3b31c6593337a097fbbd9f20d78506e490c6b64',
tags: [['p', 'f0c864cf573de171053bef4df3b31c6593337a097fbbd9f20d78506e490c6b64']],
timestamp: 1717510610413,
};
const eventId = calcEventId(event);
expect(eventId).toBe(expectEventId);
});
});
Loading

0 comments on commit 04b56c7

Please sign in to comment.