Skip to content

Commit

Permalink
new nips implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
scobru committed Dec 8, 2023
1 parent bde5a3c commit 3ff210d
Show file tree
Hide file tree
Showing 26 changed files with 1,346 additions and 520 deletions.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,45 @@ Just a quick interaction with Nostr3, and you are all set.
What's more, it links your Nostr accounts to your EVM accounts, making tipping a breeze for all Nostr accounts set up through Nostr3.
While Nostr3 isn't a full-fledged Nostr protocol client, it's a super handy tool that covers the basics.
But hey, for the best Nostr experience, we suggest pairing it with open-source clients. Happy Nostring!

Nostr3 build from Frens Client:
<https://github.com/antonio-hickey/Frens>

Nostr3 use the Nostr Band API:
<https://api.nostr.band>

Nost3 aims to extend the capabilities of the Nostr protocol by incorporating Web3 payment functionalities. This integration allows users to generate a Nostr keypair by signing a signature through their wallet. Along with this keypair, an Ethereum Virtual Machine (EVM) address will be created.

## Key Features

- **Keypair and EVM Address Generation:** Users can generate a Nostr keypair and an associated EVM address through their wallet.

- **Database Registration:** The generated EVM address can be registered in the Nostr3 database. This process links it to the user's public key within the Nostr protocol.

- **Sending and Receiving Tips:** Users can send and receive tips to and from all public keys associated with Nostr users who have registered their addresses on Nostr3.

## NIPS

- NIP-02
- NIP-04
- NIP-07
- NIP-10
- NIP-18
- NIP-25
- NIP-111

## How It Works

1. **Signature Signing:** Users sign a signature using their wallet to initiate the keypair generation process.

2. **Keypair Generation:** A Nostr keypair is generated, alongside an EVM address.

3. **Database Registration:** Users register their EVM address in the Nostr3 database, linking it to their Nostr protocol public key.

4. **Tip Transactions:** Once registered, users can engage in tip transactions with other Nostr users who have also registered on Nostr3.

## Benefits

- **Enhanced Functionality:** By bridging Nostr with Web3, users enjoy a broader range of payment options and interactions.

- **User-Friendly:** The process is designed to be straightforward, catering to both beginners and experienced users in the blockchain space.
6 changes: 3 additions & 3 deletions packages/nextjs/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const Footer = () => {
<ul className="menu menu-horizontal w-full">
<div className="flex justify-center items-center gap-2 text-sm w-full">
<div className="text-center">
<a href="https://github.com/scobru/nostr3" target="_blank" rel="noreferrer" className="link">
<a href="https://github.com/scobru/nostr3-monorepo" target="_blank" rel="noreferrer" className="link">
Fork me
</a>
</div>
Expand All @@ -65,8 +65,8 @@ export const Footer = () => {
</div>
<span>·</span>
<div className="text-center">
<a href="https://t.me/joinchat/KByvmRe5wkR-8F_zz6AjpA" target="_blank" rel="noreferrer" className="link">
Support
<a href="https://t.me/nostr3" target="_blank" rel="noreferrer" className="link">
Telegram
</a>
</div>
</div>
Expand Down
8 changes: 4 additions & 4 deletions packages/nextjs/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useCallback, useRef, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { ArchiveBoxIcon, Bars3Icon, BugAntIcon, HomeIcon, KeyIcon } from "@heroicons/react/24/outline";
import { ArchiveBoxIcon, Bars3Icon, DocumentIcon, HomeIcon, KeyIcon } from "@heroicons/react/24/outline";
import { FaucetButton, RainbowKitCustomConnectButton } from "~~/components/scaffold-eth";
import { useOutsideClick } from "~~/hooks/scaffold-eth";

Expand All @@ -27,9 +27,9 @@ export const menuLinks: HeaderMenuLink[] = [
icon: <ArchiveBoxIcon className="h-4 w-4" />,
},
{
label: "Debug Contracts",
href: "/debug",
icon: <BugAntIcon className="h-4 w-4" />,
label: "Docs",
href: "https://github.com/dostr-eth/nips/blob/ethkeygen/111.md",
icon: <DocumentIcon className="h-4 w-4" />,
},
];

Expand Down
101 changes: 82 additions & 19 deletions packages/nextjs/components/client/createPostCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Nostr3 } from "@scobru/nostr3/dist/nostr3";
import { Filter, Relay, UnsignedEvent } from "nostr-tools";
import { LazyLoadImage } from "react-lazy-load-image-component";
import { useGlobalState } from "~~/services/store/store";
import { notification } from "~~/utils/scaffold-eth";

interface CreatePostCardProps {
posterPK: string;
Expand All @@ -12,6 +13,7 @@ interface CreatePostCardProps {
publishEvent: (event: UnsignedEvent) => void;
getEvents: (filters: Filter[]) => void;
setEthTipAmount: (amount: string) => void;
handleFollowFilter: any;
}

export default function CreatePostCard(props: CreatePostCardProps) {
Expand All @@ -23,11 +25,12 @@ export default function CreatePostCard(props: CreatePostCardProps) {
const [toMe, setToMe] = useState<boolean>(false);
const [isDM, setIsDM] = useState<boolean>(false);
const nostr3 = new Nostr3(privateKey);
const setEventId = useGlobalState(state => state.setEventId);
const [proposedEventId, setProposedEventId] = useState<string>("");
const eventId = useGlobalState(state => state.eventId);
const setEvent = useGlobalState(state => state.setEvent);
const [proposedEventId, setProposedEventId] = useState<any>("");
const [proposedPubkey, setProposedPubkey] = useState<any>("");
const event = useGlobalState(state => state.event);

/* const extractHashtags = (str: string) => {
const extractHashtags = (str: string) => {
const regex = /#(\w+)/g;
let matches;
const hashtags = [];
Expand All @@ -41,25 +44,30 @@ export default function CreatePostCard(props: CreatePostCardProps) {
}

// Return the hashtags array in the desired format
return ["t", ...hashtags];
return [...hashtags];
};
*/

useEffect(() => {
if (eventId) {
setProposedEventId(eventId);
if (event) {
setProposedEventId(event.id);
setProposedPubkey(event?.pubkey);
setIsEncrypted(false);
setIsDM(false);
}
}, [eventId, proposedEventId]);
}, [event, proposedEventId]);

useEffect(() => {
props.handleFollowFilter();
}, []);

return (
<div
className={`divide-y divide-white overflow-hidden rounded-lgshadow border ${
proposedEventId ? "border-success border-2 shadow-lg shadow-success" : "border-dashed"
className={`divide-y divide-white rounded-lg shadow border-collapse border bg-base-300 ${
proposedEventId ? "border-success border-2 shadow-lg shadow-success" : " border-dashed"
}`}
>
<div
className="px-4 py-5 sm:px-6 text-lg hover:dark:bg-base-300/25 hover:!text-xl hover:cursor-pointer hover:underline hover:decoration-green-300"
className=" px-4 py-2 sm:px-6 text-lg hover:dark:bg-base-300/25 hover:!text-xl hover:cursor-pointer hover:underline hover:decoration-green-300"
onClick={() => {
const filter: Filter[] = [
{
Expand All @@ -81,6 +89,7 @@ export default function CreatePostCard(props: CreatePostCardProps) {
</span>
</div>
</div>

<div className="px-4 py-5 sm:p-6">
<div className="mt-2">
<div className="flex items-center my-4">
Expand All @@ -99,6 +108,7 @@ export default function CreatePostCard(props: CreatePostCardProps) {
placeholder="Encrypt"
onChange={e => {
setToMe(e.target.checked);
setPubKeyReceiver(props.posterPK);
}}
/>
<span>To me</span>
Expand All @@ -125,10 +135,32 @@ export default function CreatePostCard(props: CreatePostCardProps) {
) : null}
</div>

<div>
{proposedEventId ? (
<div className="my-2">
Answer to event {""}
<span className="inline-flex items-center gap-x-1.5 rounded-md bg-base-100 px-1.5 py-0.5 text-xs font-medium text-green-700">
<svg className="h-1.5 w-1.5 fill-green-500" viewBox="0 0 6 6" aria-hidden="true">
<circle cx="3" cy="3" r="3" />
</svg>
{proposedEventId.slice(proposedEventId.length - 6)}
</span>
</div>
) : null}
</div>
<button
onClick={() => {
setProposedEventId("");
setEvent("");
}}
className="btn btn-ghost btn-sm my-5"
>
reset
</button>
<textarea
name="post"
id="post"
className="block w-full h-32 bg-gray-900/25 dark:bg-white/25 rounded-lg p-1.5 text-gray-900 text-xl shadow ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-green-300 sm:leading-6 dark:text-white dark:placeholder:text-gray-200"
className="block w-full h-32 bg-gray-900/25 dark:bg-white/25 rounded-lg p-1.5 text-gray-900 text-base shadow ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-green-300 sm:leading-6 dark:text-white dark:placeholder:text-gray-200"
placeholder="Type your post..."
ref={textArea}
></textarea>
Expand All @@ -139,23 +171,53 @@ export default function CreatePostCard(props: CreatePostCardProps) {
const newEvent = {
kind: 1,
created_at: Math.floor(Date.now() / 1000),
tags: [],
tags: [] as string[][],
content: textArea?.current?.value,
pubkey: props.posterPK,
};

const hashtags = extractHashtags(String(textArea?.current?.value));

if (hashtags) {
newEvent.tags = hashtags.map(hashtag => ["t", hashtag]);
}
props.publishEvent(newEvent as UnsignedEvent);
} else if (proposedEventId) {
const newEvent = {
kind: 1,
created_at: Math.floor(Date.now() / 1000),
tags: [["e", proposedEventId]],
tags: [
["e", proposedEventId],
["p", proposedPubkey],
],
content: textArea?.current?.value,
pubkey: props.posterPK,
};

props.publishEvent(newEvent as UnsignedEvent);
} else {
} else if (toMe && isEncrypted) {
const ciphertext = await nostr3.encryptDM(textArea?.current?.value, props.posterPK);
const newEvent = {
kind: 4,
created_at: Math.floor(Date.now() / 1000),
tags: [["p", props.posterPK]],
content: ciphertext,
pubkey: props.posterPK,
};

props.publishEvent(newEvent as UnsignedEvent);
} else if (isDM) {
const ciphertext = await nostr3.encryptDM(textArea?.current?.value, pubKeyReceiver);
const newEvent = {
kind: 4,
created_at: Math.floor(Date.now() / 1000),
tags: [["p", pubKeyReceiver]],
content: ciphertext,
pubkey: props.posterPK,
};

props.publishEvent(newEvent as UnsignedEvent);
} else if (isDM && isEncrypted) {
const ciphertext = await nostr3.encryptDM(textArea?.current?.value, pubKeyReceiver);
const newEvent = {
kind: 4,
Expand All @@ -169,16 +231,17 @@ export default function CreatePostCard(props: CreatePostCardProps) {
}

setProposedEventId("");
setEventId("");
setEvent("");
notification.success("Posted");
}}
>
Publish
</button>
</div>
<div className="my-5">
<span className="text-gray-800 dark:text-gray-200">Tip with ETH</span>
<span className=" dark:text-gray-200">Tip with ETH</span>
<input
className="input input-ghost w-full my-2"
className="input input-primary w-full my-2"
placeholder="Amount to tip"
onChange={e => {
props.setEthTipAmount(e.target.value);
Expand Down
Loading

0 comments on commit 3ff210d

Please sign in to comment.