add helpers for nip-19
@ -13,6 +13,7 @@
|
||||
"@chakra-ui/react": "^2.4.4",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"bech32-buffer": "^0.2.1",
|
||||
"framer-motion": "^7.10.3",
|
||||
"idb": "^7.1.1",
|
||||
"identicon.js": "^2.3.3",
|
||||
|
1
src/components/icons/code-line.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M23 12l-7.071 7.071-1.414-1.414L20.172 12l-5.657-5.657 1.414-1.414L23 12zM3.828 12l5.657 5.657-1.414 1.414L1 12l7.071-7.071 1.414 1.414L3.828 12z"/></svg>
|
After Width: | Height: | Size: 284 B |
Before Width: | Height: | Size: 652 B After Width: | Height: | Size: 652 B |
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 290 B |
Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 580 B |
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
@ -11,10 +11,10 @@ import { useNavigate } from "react-router-dom";
|
||||
import { ErrorBoundary } from "./error-boundary";
|
||||
import { ConnectedRelays } from "./connected-relays";
|
||||
|
||||
import homeIcon from "./icons/home.svg";
|
||||
import globalIcon from "./icons/global.svg";
|
||||
import settingsIcon from "./icons/settings.svg";
|
||||
import profileIcon from "./icons/profile.svg";
|
||||
import homeIcon from "./icons/home-line.svg";
|
||||
import globalIcon from "./icons/global-line.svg";
|
||||
import settingsIcon from "./icons/settings-2-line.svg";
|
||||
import profileIcon from "./icons/user-line.svg";
|
||||
import { useIsMobile } from "../hooks/use-is-mobile";
|
||||
import { ProfileButton } from "./profile-button";
|
||||
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
Flex,
|
||||
Heading,
|
||||
HStack,
|
||||
IconButton,
|
||||
Text,
|
||||
useDisclosure,
|
||||
VStack,
|
||||
@ -21,6 +22,13 @@ import { useUserMetadata } from "../hooks/use-user-metadata";
|
||||
import { UserAvatarLink } from "./user-avatar-link";
|
||||
import { getUserFullName } from "../helpers/user-metadata";
|
||||
|
||||
import codeIcon from "./icons/code-line.svg";
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
const SimpleIcon = styled.img`
|
||||
width: 1.2em;
|
||||
`;
|
||||
|
||||
export type PostProps = {
|
||||
event: NostrEvent;
|
||||
};
|
||||
@ -47,6 +55,17 @@ export const Post = React.memo(({ event }: PostProps) => {
|
||||
<Text>{moment(event.created_at * 1000).fromNow()}</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
<IconButton
|
||||
alignSelf="flex-start"
|
||||
icon={<SimpleIcon src={codeIcon} />}
|
||||
aria-label="view raw"
|
||||
title="view raw"
|
||||
size="xs"
|
||||
variant="link"
|
||||
onClick={() =>
|
||||
window.open(`https://www.nostr.guru/e/${event.id}`, "_blank")
|
||||
}
|
||||
/>
|
||||
</HStack>
|
||||
</CardHeader>
|
||||
<CardBody pt="2" pb="0" pr="0" pl="0">
|
||||
|
@ -4,6 +4,7 @@ import useSubject from "../hooks/use-subject";
|
||||
import identity from "../services/identity";
|
||||
import { UserAvatar } from "./user-avatar";
|
||||
import { useUserMetadata } from "../hooks/use-user-metadata";
|
||||
import { normalizeToBech32 } from "../helpers/nip-19";
|
||||
|
||||
export type ProfileButtonProps = {
|
||||
to: string;
|
||||
@ -28,7 +29,7 @@ export const ProfileButton = ({ to }: ProfileButtonProps) => {
|
||||
<UserAvatar pubkey={pubkey} />
|
||||
<Box>
|
||||
<Text fontWeight="bold">{metadata?.name}</Text>
|
||||
<Text>{pubkey}</Text>
|
||||
<Text>{normalizeToBech32(pubkey)}</Text>
|
||||
</Box>
|
||||
</LinkBox>
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Tooltip } from "@chakra-ui/react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { normalizeToBech32 } from "../helpers/nip-19";
|
||||
import { useUserMetadata } from "../hooks/use-user-metadata";
|
||||
import { UserAvatar, UserAvatarProps } from "./user-avatar";
|
||||
|
||||
@ -12,7 +13,7 @@ export const UserAvatarLink = ({ pubkey, ...props }: UserAvatarProps) => {
|
||||
} else if (metadata?.name) {
|
||||
label = metadata.name;
|
||||
} else {
|
||||
label = pubkey;
|
||||
label = normalizeToBech32(pubkey) ?? pubkey;
|
||||
}
|
||||
|
||||
return (
|
||||
|
74
src/helpers/nip-19.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { decode, encode } from "bech32-buffer";
|
||||
|
||||
export function isHex(key?: string) {
|
||||
if (key?.toLowerCase()?.match(/^[0-9a-f]{64}$/)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export enum Bech32Prefix {
|
||||
Pubkey = "npub",
|
||||
SecKey = "nsec",
|
||||
Note = "note",
|
||||
}
|
||||
|
||||
export function isBech32Key(key: string) {
|
||||
try {
|
||||
let { prefix } = decode(key.toLowerCase());
|
||||
if (!["npub", "nsec", "note"].includes(prefix)) return false;
|
||||
if (!isHex(bech32ToHex(key))) return false;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function bech32ToHex(key: string) {
|
||||
try {
|
||||
let { data } = decode(key);
|
||||
return toHexString(data);
|
||||
} catch (error) {}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function hexToBech32(hex: string, prefix: Bech32Prefix) {
|
||||
try {
|
||||
let buffer = fromHexString(hex);
|
||||
return buffer && encode(prefix, buffer, "bech32");
|
||||
} catch (error) {
|
||||
// continue
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function toHexString(buffer: Uint8Array) {
|
||||
return buffer.reduce((s, byte) => {
|
||||
let hex = byte.toString(16);
|
||||
if (hex.length === 1) hex = "0" + hex;
|
||||
return s + hex;
|
||||
}, "");
|
||||
}
|
||||
|
||||
export function fromHexString(str: string) {
|
||||
if (str.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(str)) {
|
||||
return null;
|
||||
}
|
||||
let buffer = new Uint8Array(str.length / 2);
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
buffer[i] = parseInt(str.substr(2 * i, 2), 16);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export function normalizeToBech32(
|
||||
key: string,
|
||||
prefix: Bech32Prefix = Bech32Prefix.Pubkey
|
||||
) {
|
||||
if (isHex(key)) return hexToBech32(key, prefix);
|
||||
if (isBech32Key(key)) return key;
|
||||
return null;
|
||||
}
|
||||
export function normalizeToHex(hex: string) {
|
||||
if (isHex(hex)) return hex;
|
||||
if (isBech32Key(hex)) return bech32ToHex(hex);
|
||||
return null;
|
||||
}
|
9
src/helpers/nostr-event.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
|
||||
export function isReply(event: NostrEvent) {
|
||||
return !!event.tags.find((t) => t[0] === "e");
|
||||
}
|
||||
|
||||
export function isPost(event: NostrEvent) {
|
||||
return !isReply(event);
|
||||
}
|
@ -2,11 +2,16 @@ import { useEffect, useState } from "react";
|
||||
import { Subscription } from "../services/subscriptions";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
|
||||
export function useEventDir(subscription: Subscription) {
|
||||
export function useEventDir(
|
||||
subscription: Subscription,
|
||||
filter?: (event: NostrEvent) => boolean
|
||||
) {
|
||||
const [events, setEvents] = useState<Record<string, NostrEvent>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const s = subscription.onEvent.subscribe((event) => {
|
||||
if (filter && !filter(event)) return;
|
||||
|
||||
setEvents((dir) => {
|
||||
if (!dir[event.id]) {
|
||||
return { [event.id]: event, ...dir };
|
||||
|
@ -53,8 +53,8 @@ export class UserContactsService {
|
||||
)
|
||||
.subscribe(async (event) => {
|
||||
const keys = event.tags
|
||||
.filter((tag) => tag[0] === "p")
|
||||
.map((tag) => ({ pubkey: tag[1], relay: tag[2] }));
|
||||
.filter((tag) => tag[0] === "p" && tag[1])
|
||||
.map((tag) => ({ pubkey: tag[1] as string, relay: tag[2] }));
|
||||
|
||||
const relays = safeParse(
|
||||
event.content,
|
||||
|
@ -3,7 +3,7 @@ export type NostrEvent = {
|
||||
pubkey: string;
|
||||
created_at: number;
|
||||
kind: number;
|
||||
tags: [string] | [string, string] | [string, string, string];
|
||||
tags: ([string] | [string, string] | [string, string, string])[];
|
||||
content: string;
|
||||
sig: string;
|
||||
};
|
||||
|
@ -1,34 +1,23 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Flex, SkeletonText } from "@chakra-ui/react";
|
||||
import {
|
||||
Flex,
|
||||
SkeletonText,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanel,
|
||||
TabPanels,
|
||||
Tabs,
|
||||
} from "@chakra-ui/react";
|
||||
import { useSubscription } from "../../hooks/use-subscription";
|
||||
import { Post } from "../../components/post";
|
||||
import moment from "moment/moment";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import settings from "../../services/settings";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useEventDir } from "../../hooks/use-event-dir";
|
||||
import { Subscription } from "../../services/subscriptions";
|
||||
import { isPost, isReply } from "../../helpers/nostr-event";
|
||||
|
||||
export const GlobalView = () => {
|
||||
const relays = useSubject(settings.relays);
|
||||
const [events, setEvents] = useState<Record<string, NostrEvent>>({});
|
||||
|
||||
const sub = useSubscription(
|
||||
relays,
|
||||
{ kinds: [1], limit: 10, since: moment().startOf("day").valueOf() / 1000 },
|
||||
"global-events"
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const s = sub.onEvent.subscribe((event) => {
|
||||
setEvents((dir) => {
|
||||
if (!dir[event.id]) {
|
||||
return { [event.id]: event, ...dir };
|
||||
}
|
||||
return dir;
|
||||
});
|
||||
});
|
||||
|
||||
return () => s.unsubscribe();
|
||||
}, [sub]);
|
||||
const PostsTimeline = ({ sub }: { sub: Subscription }) => {
|
||||
const { events } = useEventDir(sub, isPost);
|
||||
|
||||
const timeline = Object.values(events).sort(
|
||||
(a, b) => b.created_at - a.created_at
|
||||
@ -41,10 +30,65 @@ export const GlobalView = () => {
|
||||
if (timeline.length > 20) timeline.length = 20;
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2" overflow="auto" height="100%" padding="2">
|
||||
<Flex direction="column" gap="2">
|
||||
{timeline.map((event) => (
|
||||
<Post key={event.id} event={event} />
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const RepliesTimeline = ({ sub }: { sub: Subscription }) => {
|
||||
const { events } = useEventDir(sub, isReply);
|
||||
|
||||
const timeline = Object.values(events).sort(
|
||||
(a, b) => b.created_at - a.created_at
|
||||
);
|
||||
|
||||
if (timeline.length === 0) {
|
||||
return <SkeletonText />;
|
||||
}
|
||||
|
||||
if (timeline.length > 20) timeline.length = 20;
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2">
|
||||
{timeline.map((event) => (
|
||||
<Post key={event.id} event={event} />
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const GlobalView = () => {
|
||||
const relays = useSubject(settings.relays);
|
||||
|
||||
const sub = useSubscription(
|
||||
relays,
|
||||
{ kinds: [1], limit: 10, since: moment().startOf("day").valueOf() / 1000 },
|
||||
"global-events"
|
||||
);
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
flexGrow="1"
|
||||
overflow="hidden"
|
||||
isManual
|
||||
>
|
||||
<TabList>
|
||||
<Tab>Posts</Tab>
|
||||
<Tab>Replies</Tab>
|
||||
</TabList>
|
||||
<TabPanels overflow="auto" height="100%">
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<PostsTimeline sub={sub} />
|
||||
</TabPanel>
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<RepliesTimeline sub={sub} />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
@ -1,25 +1,28 @@
|
||||
import { HStack } from "@chakra-ui/react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { UserAvatar } from "../components/user-avatar";
|
||||
import { UserAvatarLink } from "../components/user-avatar-link";
|
||||
import { normalizeToHex } from "../helpers/nip-19";
|
||||
|
||||
export const HomeView = () => {
|
||||
return (
|
||||
<>
|
||||
<HStack spacing=".5rem">
|
||||
<UserAvatarLink pubkey="32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" />
|
||||
<UserAvatarLink pubkey="6b0d4c8d9dc59e110d380b0429a02891f1341a0fa2ba1b1cf83a3db4d47e3964" />
|
||||
<UserAvatarLink pubkey="00000000827ffaa94bfea288c3dfce4422c794fbb96625b6b31e9049f729d700" />
|
||||
<UserAvatarLink pubkey="82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2" />
|
||||
<UserAvatarLink pubkey="85080d3bad70ccdcd7f74c29a44f55bb85cbcd3dd0cbb957da1d215bdb931204" />
|
||||
<UserAvatarLink pubkey="e88a691e98d9987c964521dff60025f60700378a4879180dcbbb4a5027850411" />
|
||||
<UserAvatarLink pubkey="8c0da4862130283ff9e67d889df264177a508974e2feb96de139804ea66d6168" />
|
||||
<UserAvatarLink pubkey="c4eabae1be3cf657bc1855ee05e69de9f059cb7a059227168b80b89761cbc4e0" />
|
||||
<UserAvatarLink pubkey="e33fe65f1fde44c6dc17eeb38fdad0fceaf1cae8722084332ed1e32496291d42" />
|
||||
<UserAvatarLink pubkey="c5cfda98d01f152b3493d995eed4cdb4d9e55a973925f6f9ea24769a5a21e778" />
|
||||
<UserAvatarLink pubkey="3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d" />
|
||||
<UserAvatarLink pubkey="04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9" />
|
||||
</HStack>
|
||||
</>
|
||||
<HStack spacing=".5rem">
|
||||
<UserAvatarLink pubkey="32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" />
|
||||
<UserAvatarLink
|
||||
pubkey={
|
||||
normalizeToHex(
|
||||
"npub1dergggklka99wwrs92yz8wdjs952h2ux2ha2ed598ngwu9w7a6fsh9xzpc"
|
||||
) ?? ""
|
||||
}
|
||||
/>
|
||||
<UserAvatarLink pubkey="00000000827ffaa94bfea288c3dfce4422c794fbb96625b6b31e9049f729d700" />
|
||||
<UserAvatarLink pubkey="82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2" />
|
||||
<UserAvatarLink pubkey="85080d3bad70ccdcd7f74c29a44f55bb85cbcd3dd0cbb957da1d215bdb931204" />
|
||||
<UserAvatarLink pubkey="e88a691e98d9987c964521dff60025f60700378a4879180dcbbb4a5027850411" />
|
||||
<UserAvatarLink pubkey="8c0da4862130283ff9e67d889df264177a508974e2feb96de139804ea66d6168" />
|
||||
<UserAvatarLink pubkey="c4eabae1be3cf657bc1855ee05e69de9f059cb7a059227168b80b89761cbc4e0" />
|
||||
<UserAvatarLink pubkey="e33fe65f1fde44c6dc17eeb38fdad0fceaf1cae8722084332ed1e32496291d42" />
|
||||
<UserAvatarLink pubkey="c5cfda98d01f152b3493d995eed4cdb4d9e55a973925f6f9ea24769a5a21e778" />
|
||||
<UserAvatarLink pubkey="3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d" />
|
||||
<UserAvatarLink pubkey="04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9" />
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
@ -17,17 +17,23 @@ import { getUserFullName } from "../../helpers/user-metadata";
|
||||
import { useIsMobile } from "../../hooks/use-is-mobile";
|
||||
import { UserRelaysTab } from "./relays";
|
||||
import { UserFollowingTab } from "./following";
|
||||
import { UserRepliesTab } from "./replies";
|
||||
import { normalizeToBech32, normalizeToHex } from "../../helpers/nip-19";
|
||||
|
||||
export const UserView = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const { pubkey } = useParams();
|
||||
if (!pubkey) {
|
||||
const params = useParams();
|
||||
|
||||
if (!params.pubkey) {
|
||||
// TODO: better 404
|
||||
throw new Error("No pubkey");
|
||||
}
|
||||
|
||||
const pubkey = normalizeToHex(params.pubkey) ?? "";
|
||||
|
||||
const { metadata, loading: loadingMetadata } = useUserMetadata(pubkey, true);
|
||||
const label = metadata ? getUserFullName(metadata) || pubkey : pubkey;
|
||||
const bech32Key = normalizeToBech32(pubkey);
|
||||
const label = metadata ? getUserFullName(metadata) || bech32Key : bech32Key;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@ -49,9 +55,11 @@ export const UserView = () => {
|
||||
flexDirection="column"
|
||||
flexGrow="1"
|
||||
overflow="hidden"
|
||||
isManual
|
||||
>
|
||||
<TabList>
|
||||
<Tab>Posts</Tab>
|
||||
<Tab>Replies</Tab>
|
||||
<Tab>Following</Tab>
|
||||
<Tab>Relays</Tab>
|
||||
</TabList>
|
||||
@ -60,6 +68,9 @@ export const UserView = () => {
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<UserPostsTab pubkey={pubkey} />
|
||||
</TabPanel>
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<UserRepliesTab pubkey={pubkey} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<UserFollowingTab pubkey={pubkey} />
|
||||
</TabPanel>
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { Flex, SkeletonText } from "@chakra-ui/react";
|
||||
import { useSubscription } from "../../hooks/use-subscription";
|
||||
import { Post } from "../../components/post";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import settings from "../../services/settings";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useEventDir } from "../../hooks/use-event-dir";
|
||||
@ -16,7 +15,10 @@ export const UserPostsTab = ({ pubkey }: { pubkey: string }) => {
|
||||
`${pubkey} posts`
|
||||
);
|
||||
|
||||
const { events, reset } = useEventDir(sub);
|
||||
const { events, reset } = useEventDir(
|
||||
sub,
|
||||
(event) => !event.tags.find((t) => t[0] === "e")
|
||||
);
|
||||
|
||||
// clear events when pubkey changes
|
||||
useEffect(() => reset(), [pubkey]);
|
||||
|
43
src/views/user/replies.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { useEffect } from "react";
|
||||
import { Flex, SkeletonText } from "@chakra-ui/react";
|
||||
import { useSubscription } from "../../hooks/use-subscription";
|
||||
import { Post } from "../../components/post";
|
||||
import settings from "../../services/settings";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useEventDir } from "../../hooks/use-event-dir";
|
||||
|
||||
export const UserRepliesTab = ({ pubkey }: { pubkey: string }) => {
|
||||
const relays = useSubject(settings.relays);
|
||||
|
||||
const sub = useSubscription(
|
||||
relays,
|
||||
{ authors: [pubkey], kinds: [1] },
|
||||
`${pubkey} posts`
|
||||
);
|
||||
|
||||
const { events, reset } = useEventDir(
|
||||
sub,
|
||||
(event) => !!event.tags.find((t) => t[0] === "e")
|
||||
);
|
||||
|
||||
// clear events when pubkey changes
|
||||
useEffect(() => reset(), [pubkey]);
|
||||
|
||||
const timeline = Object.values(events).sort(
|
||||
(a, b) => b.created_at - a.created_at
|
||||
);
|
||||
|
||||
if (timeline.length === 0) {
|
||||
return <SkeletonText />;
|
||||
}
|
||||
|
||||
if (timeline.length > 20) timeline.length = 20;
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2" pr="2" pl="2">
|
||||
{timeline.map((event) => (
|
||||
<Post key={event.id} event={event} />
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
};
|
@ -2731,6 +2731,11 @@ balanced-match@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
|
||||
bech32-buffer@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/bech32-buffer/-/bech32-buffer-0.2.1.tgz#8106f2f51bcb2ba1d9fb7718905c3042c5be2fcd"
|
||||
integrity sha512-fCG1TyZuCN48Sdw97p/IR39fvqpFlWDVpG7qnuU1Uc3+Xtc/0uqAp8U7bMW/bGuVF5CcNVIXwxQsWwUr6un6FQ==
|
||||
|
||||
better-path-resolve@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/better-path-resolve/-/better-path-resolve-1.0.0.tgz#13a35a1104cdd48a7b74bf8758f96a1ee613f99d"
|
||||
|