commit
77b92eddf3
@ -9,7 +9,7 @@ Snort supports the following NIP's:
|
|||||||
- [ ] NIP-03: OpenTimestamps Attestations for Events
|
- [ ] NIP-03: OpenTimestamps Attestations for Events
|
||||||
- [x] NIP-04: Encrypted Direct Message
|
- [x] NIP-04: Encrypted Direct Message
|
||||||
- [x] NIP-05: Mapping Nostr keys to DNS-based internet identifiers
|
- [x] NIP-05: Mapping Nostr keys to DNS-based internet identifiers
|
||||||
- [ ] NIP-06: Basic key derivation from mnemonic seed phrase
|
- [x] NIP-06: Basic key derivation from mnemonic seed phrase
|
||||||
- [x] NIP-07: `window.nostr` capability for web browsers
|
- [x] NIP-07: `window.nostr` capability for web browsers
|
||||||
- [x] NIP-08: Handling Mentions
|
- [x] NIP-08: Handling Mentions
|
||||||
- [x] NIP-09: Event Deletion
|
- [x] NIP-09: Event Deletion
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
"@noble/secp256k1": "^1.7.0",
|
"@noble/secp256k1": "^1.7.0",
|
||||||
"@protobufjs/base64": "^1.1.2",
|
"@protobufjs/base64": "^1.1.2",
|
||||||
"@reduxjs/toolkit": "^1.9.1",
|
"@reduxjs/toolkit": "^1.9.1",
|
||||||
|
"@scure/bip32": "^1.1.5",
|
||||||
|
"@scure/bip39": "^1.1.1",
|
||||||
"@snort/nostr": "^1.0.0",
|
"@snort/nostr": "^1.0.0",
|
||||||
"@szhsin/react-menu": "^3.3.1",
|
"@szhsin/react-menu": "^3.3.1",
|
||||||
"base32-decode": "^1.0.0",
|
"base32-decode": "^1.0.0",
|
||||||
|
@ -85,6 +85,11 @@ export const ZapperSpam = [
|
|||||||
"e1ff3bfdd4e40315959b08b4fcc8245eaa514637e1d4ec2ae166b743341be1af", // benthecarman
|
"e1ff3bfdd4e40315959b08b4fcc8245eaa514637e1d4ec2ae166b743341be1af", // benthecarman
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NIP06-defined derivation path for private keys
|
||||||
|
*/
|
||||||
|
export const DerivationPath = "m/44'/1237'/0'/0/0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regex to match email address
|
* Regex to match email address
|
||||||
*/
|
*/
|
||||||
@ -92,6 +97,11 @@ export const EmailRegex =
|
|||||||
// eslint-disable-next-line no-useless-escape
|
// eslint-disable-next-line no-useless-escape
|
||||||
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regex to match a mnemonic seed
|
||||||
|
*/
|
||||||
|
export const MnemonicRegex = /^([^\s]+\s){11}[^\s]+$/;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract file extensions regex
|
* Extract file extensions regex
|
||||||
*/
|
*/
|
||||||
|
@ -8,8 +8,8 @@ import { useIntl, FormattedMessage } from "react-intl";
|
|||||||
|
|
||||||
import { RootState } from "State/Store";
|
import { RootState } from "State/Store";
|
||||||
import { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
|
import { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
|
||||||
import { DefaultRelays, EmailRegex } from "Const";
|
import { DefaultRelays, EmailRegex, MnemonicRegex } from "Const";
|
||||||
import { bech32ToHex, unwrap } from "Util";
|
import { bech32ToHex, generateBip39Entropy, entropyToDerivedKey, unwrap } from "Util";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/nostr";
|
||||||
import ZapButton from "Element/ZapButton";
|
import ZapButton from "Element/ZapButton";
|
||||||
// import useImgProxy from "Feed/ImgProxy";
|
// import useImgProxy from "Feed/ImgProxy";
|
||||||
@ -97,12 +97,14 @@ export default function LoginPage() {
|
|||||||
} else if (key.match(EmailRegex)) {
|
} else if (key.match(EmailRegex)) {
|
||||||
const hexKey = await getNip05PubKey(key);
|
const hexKey = await getNip05PubKey(key);
|
||||||
dispatch(setPublicKey(hexKey));
|
dispatch(setPublicKey(hexKey));
|
||||||
|
} else if (key.match(MnemonicRegex)) {
|
||||||
|
const ent = generateBip39Entropy(key);
|
||||||
|
const keyHex = entropyToDerivedKey(ent);
|
||||||
|
dispatch(setPrivateKey(keyHex));
|
||||||
|
} else if (secp.utils.isValidPrivateKey(key)) {
|
||||||
|
dispatch(setPrivateKey(key));
|
||||||
} else {
|
} else {
|
||||||
if (secp.utils.isValidPrivateKey(key)) {
|
throw new Error("INVALID PRIVATE KEY");
|
||||||
dispatch(setPrivateKey(key));
|
|
||||||
} else {
|
|
||||||
throw new Error("INVALID PRIVATE KEY");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(`Failed to load NIP-05 pub key (${e})`);
|
setError(`Failed to load NIP-05 pub key (${e})`);
|
||||||
@ -111,8 +113,10 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function makeRandomKey() {
|
async function makeRandomKey() {
|
||||||
const newKey = secp.utils.bytesToHex(secp.utils.randomPrivateKey());
|
const ent = generateBip39Entropy();
|
||||||
dispatch(setGeneratedPrivateKey(newKey));
|
const entHex = secp.utils.bytesToHex(ent);
|
||||||
|
const newKeyHex = entropyToDerivedKey(ent);
|
||||||
|
dispatch(setGeneratedPrivateKey({ key: newKeyHex, entropy: entHex }));
|
||||||
navigate("/new");
|
navigate("/new");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,5 +46,5 @@ export default defineMessages({
|
|||||||
},
|
},
|
||||||
Bookmarks: { defaultMessage: "Bookmarks" },
|
Bookmarks: { defaultMessage: "Bookmarks" },
|
||||||
BookmarksCount: { defaultMessage: "{n} Bookmarks" },
|
BookmarksCount: { defaultMessage: "{n} Bookmarks" },
|
||||||
KeyPlaceholder: { defaultMessage: "nsec, npub, nip-05, hex" },
|
KeyPlaceholder: { defaultMessage: "nsec, npub, nip-05, hex, mnemonic" },
|
||||||
});
|
});
|
||||||
|
@ -1,19 +1,27 @@
|
|||||||
import { useIntl, FormattedMessage } from "react-intl";
|
import { useIntl, FormattedMessage } from "react-intl";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
import { useNavigate, Link } from "react-router-dom";
|
import { useNavigate, Link } from "react-router-dom";
|
||||||
import { RecommendedFollows } from "Const";
|
import { RecommendedFollows } from "Const";
|
||||||
import Logo from "Element/Logo";
|
import Logo from "Element/Logo";
|
||||||
import FollowListBase from "Element/FollowListBase";
|
import FollowListBase from "Element/FollowListBase";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import { clearEntropy } from "State/Login";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
|
||||||
export default function DiscoverFollows() {
|
export default function DiscoverFollows() {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const sortedReccomends = useMemo(() => {
|
const sortedReccomends = useMemo(() => {
|
||||||
return RecommendedFollows.sort(() => (Math.random() >= 0.5 ? -1 : 1));
|
return RecommendedFollows.sort(() => (Math.random() >= 0.5 ? -1 : 1));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
async function clearEntropyAndGo() {
|
||||||
|
dispatch(clearEntropy());
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="main-content new-user" dir="auto">
|
<div className="main-content new-user" dir="auto">
|
||||||
<Logo />
|
<Logo />
|
||||||
@ -27,7 +35,7 @@ export default function DiscoverFollows() {
|
|||||||
<FormattedMessage {...messages.Share} values={{ link: <Link to="/">{formatMessage(messages.World)}</Link> }} />
|
<FormattedMessage {...messages.Share} values={{ link: <Link to="/">{formatMessage(messages.World)}</Link> }} />
|
||||||
</p>
|
</p>
|
||||||
<div className="next-actions continue-actions">
|
<div className="next-actions continue-actions">
|
||||||
<button type="button" onClick={() => navigate("/")}>
|
<button type="button" onClick={() => clearEntropyAndGo()}>
|
||||||
<FormattedMessage {...messages.Done} />{" "}
|
<FormattedMessage {...messages.Done} />{" "}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,7 +6,7 @@ import Logo from "Element/Logo";
|
|||||||
import { CollapsedSection } from "Element/Collapsed";
|
import { CollapsedSection } from "Element/Collapsed";
|
||||||
import Copy from "Element/Copy";
|
import Copy from "Element/Copy";
|
||||||
import { RootState } from "State/Store";
|
import { RootState } from "State/Store";
|
||||||
import { hexToBech32 } from "Util";
|
import { hexToBech32, hexToMnemonic } from "Util";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ const Extensions = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function NewUserFlow() {
|
export default function NewUserFlow() {
|
||||||
const { publicKey, privateKey } = useSelector((s: RootState) => s.login);
|
const { publicKey, privateKey, generatedEntropy } = useSelector((s: RootState) => s.login);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -91,6 +91,10 @@ export default function NewUserFlow() {
|
|||||||
<FormattedMessage {...messages.YourPrivkey} />
|
<FormattedMessage {...messages.YourPrivkey} />
|
||||||
</h2>
|
</h2>
|
||||||
<Copy text={hexToBech32("nsec", privateKey ?? "")} />
|
<Copy text={hexToBech32("nsec", privateKey ?? "")} />
|
||||||
|
<h2>
|
||||||
|
<FormattedMessage {...messages.YourMnemonic} />
|
||||||
|
</h2>
|
||||||
|
<Copy text={hexToMnemonic(generatedEntropy ?? "")} />
|
||||||
<div className="next-actions">
|
<div className="next-actions">
|
||||||
<button type="button" onClick={() => navigate("/new/username")}>
|
<button type="button" onClick={() => navigate("/new/username")}>
|
||||||
<FormattedMessage {...messages.KeysSaved} />{" "}
|
<FormattedMessage {...messages.KeysSaved} />{" "}
|
||||||
|
@ -10,6 +10,7 @@ export default defineMessages({
|
|||||||
},
|
},
|
||||||
YourPubkey: { defaultMessage: "Your public key" },
|
YourPubkey: { defaultMessage: "Your public key" },
|
||||||
YourPrivkey: { defaultMessage: "Your private key" },
|
YourPrivkey: { defaultMessage: "Your private key" },
|
||||||
|
YourMnemonic: { defaultMessage: "Your mnemonic phrase" },
|
||||||
KeysSaved: { defaultMessage: "I have saved my keys, continue" },
|
KeysSaved: { defaultMessage: "I have saved my keys, continue" },
|
||||||
WhatIsSnort: { defaultMessage: "What is Snort and how does it work?" },
|
WhatIsSnort: { defaultMessage: "What is Snort and how does it work?" },
|
||||||
WhatIsSnortIntro: {
|
WhatIsSnortIntro: {
|
||||||
|
@ -98,6 +98,11 @@ export interface LoginStore {
|
|||||||
*/
|
*/
|
||||||
privateKey?: HexKey;
|
privateKey?: HexKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BIP39-generated, hex-encoded entropy
|
||||||
|
*/
|
||||||
|
generatedEntropy?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current users public key
|
* Current users public key
|
||||||
*/
|
*/
|
||||||
@ -253,6 +258,11 @@ export interface SetFollowsPayload {
|
|||||||
createdAt: number;
|
createdAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SetGeneratedKeyPayload {
|
||||||
|
key: HexKey;
|
||||||
|
entropy: HexKey;
|
||||||
|
}
|
||||||
|
|
||||||
export const ReadPreferences = () => {
|
export const ReadPreferences = () => {
|
||||||
const pref = window.localStorage.getItem(UserPreferencesKey);
|
const pref = window.localStorage.getItem(UserPreferencesKey);
|
||||||
if (pref) {
|
if (pref) {
|
||||||
@ -315,12 +325,16 @@ const LoginSlice = createSlice({
|
|||||||
window.localStorage.setItem(PrivateKeyItem, action.payload);
|
window.localStorage.setItem(PrivateKeyItem, action.payload);
|
||||||
state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(action.payload));
|
state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(action.payload));
|
||||||
},
|
},
|
||||||
setGeneratedPrivateKey: (state, action: PayloadAction<HexKey>) => {
|
setGeneratedPrivateKey: (state, action: PayloadAction<SetGeneratedKeyPayload>) => {
|
||||||
state.loggedOut = false;
|
state.loggedOut = false;
|
||||||
state.newUserKey = true;
|
state.newUserKey = true;
|
||||||
state.privateKey = action.payload;
|
state.privateKey = action.payload.key;
|
||||||
window.localStorage.setItem(PrivateKeyItem, action.payload);
|
state.generatedEntropy = action.payload.entropy;
|
||||||
state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(action.payload));
|
window.localStorage.setItem(PrivateKeyItem, action.payload.key);
|
||||||
|
state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(action.payload.key));
|
||||||
|
},
|
||||||
|
clearEntropy: state => {
|
||||||
|
state.generatedEntropy = undefined;
|
||||||
},
|
},
|
||||||
setPublicKey: (state, action: PayloadAction<HexKey>) => {
|
setPublicKey: (state, action: PayloadAction<HexKey>) => {
|
||||||
window.localStorage.setItem(PublicKeyItem, action.payload);
|
window.localStorage.setItem(PublicKeyItem, action.payload);
|
||||||
@ -468,6 +482,7 @@ export const {
|
|||||||
init,
|
init,
|
||||||
setPrivateKey,
|
setPrivateKey,
|
||||||
setGeneratedPrivateKey,
|
setGeneratedPrivateKey,
|
||||||
|
clearEntropy,
|
||||||
setPublicKey,
|
setPublicKey,
|
||||||
setRelays,
|
setRelays,
|
||||||
removeRelay,
|
removeRelay,
|
||||||
|
@ -5,7 +5,11 @@ import { decode as invoiceDecode } from "light-bolt11-decoder";
|
|||||||
import { bech32 } from "bech32";
|
import { bech32 } from "bech32";
|
||||||
import base32Decode from "base32-decode";
|
import base32Decode from "base32-decode";
|
||||||
import { HexKey, TaggedRawEvent, u256, EventKind, encodeTLV, NostrPrefix } from "@snort/nostr";
|
import { HexKey, TaggedRawEvent, u256, EventKind, encodeTLV, NostrPrefix } from "@snort/nostr";
|
||||||
|
import * as bip39 from "@scure/bip39";
|
||||||
|
import { wordlist } from "@scure/bip39/wordlists/english";
|
||||||
|
import { HDKey } from "@scure/bip32";
|
||||||
|
|
||||||
|
import { DerivationPath } from "Const";
|
||||||
import { MetadataCache } from "State/Users";
|
import { MetadataCache } from "State/Users";
|
||||||
|
|
||||||
export const sha256 = (str: string) => {
|
export const sha256 = (str: string) => {
|
||||||
@ -100,6 +104,39 @@ export function hexToBech32(hrp: string, hex?: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateBip39Entropy(mnemonic?: string): Uint8Array {
|
||||||
|
try {
|
||||||
|
const mn = mnemonic ?? bip39.generateMnemonic(wordlist);
|
||||||
|
return bip39.mnemonicToEntropy(mn, wordlist);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error("INVALID MNEMONIC PHRASE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert hex-encoded entropy into mnemonic phrase
|
||||||
|
*/
|
||||||
|
export function hexToMnemonic(hex: string): string {
|
||||||
|
const bytes = secp.utils.hexToBytes(hex);
|
||||||
|
return bip39.entropyToMnemonic(bytes, wordlist);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert mnemonic phrase into hex-encoded private key
|
||||||
|
* using the derivation path specified in NIP06
|
||||||
|
* @param mnemonic the mnemonic-encoded entropy
|
||||||
|
*/
|
||||||
|
export function entropyToDerivedKey(entropy: Uint8Array): string {
|
||||||
|
const masterKey = HDKey.fromMasterSeed(entropy);
|
||||||
|
const newKey = masterKey.derive(DerivationPath);
|
||||||
|
|
||||||
|
if (!newKey.privateKey) {
|
||||||
|
throw new Error("INVALID KEY DERIVATION");
|
||||||
|
}
|
||||||
|
|
||||||
|
return secp.utils.bytesToHex(newKey.privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert hex pubkey to bech32 link url
|
* Convert hex pubkey to bech32 link url
|
||||||
*/
|
*/
|
||||||
|
@ -197,9 +197,6 @@
|
|||||||
"B6+XJy": {
|
"B6+XJy": {
|
||||||
"defaultMessage": "zapped"
|
"defaultMessage": "zapped"
|
||||||
},
|
},
|
||||||
"B6H7eJ": {
|
|
||||||
"defaultMessage": "nsec, npub, nip-05, hex"
|
|
||||||
},
|
|
||||||
"BOUMjw": {
|
"BOUMjw": {
|
||||||
"defaultMessage": "No nostr users found for {twitterUsername}"
|
"defaultMessage": "No nostr users found for {twitterUsername}"
|
||||||
},
|
},
|
||||||
@ -312,6 +309,9 @@
|
|||||||
"IEwZvs": {
|
"IEwZvs": {
|
||||||
"defaultMessage": "Are you sure you want to unpin this note?"
|
"defaultMessage": "Are you sure you want to unpin this note?"
|
||||||
},
|
},
|
||||||
|
"IKOPx/": {
|
||||||
|
"defaultMessage": "Donate Page"
|
||||||
|
},
|
||||||
"INSqIz": {
|
"INSqIz": {
|
||||||
"defaultMessage": "Twitter username..."
|
"defaultMessage": "Twitter username..."
|
||||||
},
|
},
|
||||||
@ -327,6 +327,9 @@
|
|||||||
"JHEHCk": {
|
"JHEHCk": {
|
||||||
"defaultMessage": "Zaps ({n})"
|
"defaultMessage": "Zaps ({n})"
|
||||||
},
|
},
|
||||||
|
"JXtsQW": {
|
||||||
|
"defaultMessage": "Fast Zap Donation"
|
||||||
|
},
|
||||||
"JkLHGw": {
|
"JkLHGw": {
|
||||||
"defaultMessage": "Website"
|
"defaultMessage": "Website"
|
||||||
},
|
},
|
||||||
@ -348,6 +351,9 @@
|
|||||||
"KahimY": {
|
"KahimY": {
|
||||||
"defaultMessage": "Unknown event kind: {kind}"
|
"defaultMessage": "Unknown event kind: {kind}"
|
||||||
},
|
},
|
||||||
|
"L7SZPr": {
|
||||||
|
"defaultMessage": "For more information about donations see {link}."
|
||||||
|
},
|
||||||
"LXxsbk": {
|
"LXxsbk": {
|
||||||
"defaultMessage": "Anonymous"
|
"defaultMessage": "Anonymous"
|
||||||
},
|
},
|
||||||
@ -410,6 +416,9 @@
|
|||||||
"PCSt5T": {
|
"PCSt5T": {
|
||||||
"defaultMessage": "Preferences"
|
"defaultMessage": "Preferences"
|
||||||
},
|
},
|
||||||
|
"PLSbmL": {
|
||||||
|
"defaultMessage": "Your mnemonic phrase"
|
||||||
|
},
|
||||||
"Pe0ogR": {
|
"Pe0ogR": {
|
||||||
"defaultMessage": "Theme"
|
"defaultMessage": "Theme"
|
||||||
},
|
},
|
||||||
@ -439,6 +448,9 @@
|
|||||||
"RahCRH": {
|
"RahCRH": {
|
||||||
"defaultMessage": "Expired"
|
"defaultMessage": "Expired"
|
||||||
},
|
},
|
||||||
|
"RfhLwC": {
|
||||||
|
"defaultMessage": "By: {author}"
|
||||||
|
},
|
||||||
"RhDAoS": {
|
"RhDAoS": {
|
||||||
"defaultMessage": "Are you sure you want to delete {id}"
|
"defaultMessage": "Are you sure you want to delete {id}"
|
||||||
},
|
},
|
||||||
@ -492,6 +504,9 @@
|
|||||||
"WxthCV": {
|
"WxthCV": {
|
||||||
"defaultMessage": "e.g. Jack"
|
"defaultMessage": "e.g. Jack"
|
||||||
},
|
},
|
||||||
|
"X7xU8J": {
|
||||||
|
"defaultMessage": "nsec, npub, nip-05, hex, mnemonic"
|
||||||
|
},
|
||||||
"XgWvGA": {
|
"XgWvGA": {
|
||||||
"defaultMessage": "Reactions"
|
"defaultMessage": "Reactions"
|
||||||
},
|
},
|
||||||
@ -599,6 +614,9 @@
|
|||||||
"gjBiyj": {
|
"gjBiyj": {
|
||||||
"defaultMessage": "Loading..."
|
"defaultMessage": "Loading..."
|
||||||
},
|
},
|
||||||
|
"h8XMJL": {
|
||||||
|
"defaultMessage": "Badges"
|
||||||
|
},
|
||||||
"hCUivF": {
|
"hCUivF": {
|
||||||
"defaultMessage": "Notes will stream in real time into global and posts tab"
|
"defaultMessage": "Notes will stream in real time into global and posts tab"
|
||||||
},
|
},
|
||||||
@ -782,6 +800,9 @@
|
|||||||
"rudscU": {
|
"rudscU": {
|
||||||
"defaultMessage": "Failed to load follows, please try again later"
|
"defaultMessage": "Failed to load follows, please try again later"
|
||||||
},
|
},
|
||||||
|
"sBz4+I": {
|
||||||
|
"defaultMessage": "For each Fast Zap an additional {percentage}% ({amount} sats) of the zap amount will be sent to the Snort developers as a donation."
|
||||||
|
},
|
||||||
"sWnYKw": {
|
"sWnYKw": {
|
||||||
"defaultMessage": "Snort is designed to have a similar experience to Twitter."
|
"defaultMessage": "Snort is designed to have a similar experience to Twitter."
|
||||||
},
|
},
|
||||||
|
@ -63,7 +63,6 @@
|
|||||||
"AyGauy": "Login",
|
"AyGauy": "Login",
|
||||||
"B4C47Y": "name too short",
|
"B4C47Y": "name too short",
|
||||||
"B6+XJy": "zapped",
|
"B6+XJy": "zapped",
|
||||||
"B6H7eJ": "nsec, npub, nip-05, hex",
|
|
||||||
"BOUMjw": "No nostr users found for {twitterUsername}",
|
"BOUMjw": "No nostr users found for {twitterUsername}",
|
||||||
"BOr9z/": "Snort is an open source project built by passionate people in their free time",
|
"BOr9z/": "Snort is an open source project built by passionate people in their free time",
|
||||||
"BcGMo+": "Notes hold text content, the most popular usage of these notes is to store \"tweet like\" messages.",
|
"BcGMo+": "Notes hold text content, the most popular usage of these notes is to store \"tweet like\" messages.",
|
||||||
@ -101,11 +100,13 @@
|
|||||||
"HOzFdo": "Muted",
|
"HOzFdo": "Muted",
|
||||||
"HbefNb": "Open Wallet",
|
"HbefNb": "Open Wallet",
|
||||||
"IEwZvs": "Are you sure you want to unpin this note?",
|
"IEwZvs": "Are you sure you want to unpin this note?",
|
||||||
|
"IKOPx/": "Donate Page",
|
||||||
"INSqIz": "Twitter username...",
|
"INSqIz": "Twitter username...",
|
||||||
"IUZC+0": "This means that nobody can modify notes which you have created and everybody can easily verify that the notes they are reading are created by you.",
|
"IUZC+0": "This means that nobody can modify notes which you have created and everybody can easily verify that the notes they are reading are created by you.",
|
||||||
"Iwm6o2": "NIP-05 Shop",
|
"Iwm6o2": "NIP-05 Shop",
|
||||||
"JCIgkj": "Username",
|
"JCIgkj": "Username",
|
||||||
"JHEHCk": "Zaps ({n})",
|
"JHEHCk": "Zaps ({n})",
|
||||||
|
"JXtsQW": "Fast Zap Donation",
|
||||||
"JkLHGw": "Website",
|
"JkLHGw": "Website",
|
||||||
"K3r6DQ": "Delete",
|
"K3r6DQ": "Delete",
|
||||||
"K7AkdL": "Show",
|
"K7AkdL": "Show",
|
||||||
@ -113,6 +114,7 @@
|
|||||||
"KQvWvD": "Deleted",
|
"KQvWvD": "Deleted",
|
||||||
"KWuDfz": "I have saved my keys, continue",
|
"KWuDfz": "I have saved my keys, continue",
|
||||||
"KahimY": "Unknown event kind: {kind}",
|
"KahimY": "Unknown event kind: {kind}",
|
||||||
|
"L7SZPr": "For more information about donations see {link}.",
|
||||||
"LXxsbk": "Anonymous",
|
"LXxsbk": "Anonymous",
|
||||||
"LgbKvU": "Comment",
|
"LgbKvU": "Comment",
|
||||||
"LxY9tW": "Generate Key",
|
"LxY9tW": "Generate Key",
|
||||||
@ -133,6 +135,7 @@
|
|||||||
"P7FD0F": "System (Default)",
|
"P7FD0F": "System (Default)",
|
||||||
"P7nJT9": "Total today (UTC): {amount} sats",
|
"P7nJT9": "Total today (UTC): {amount} sats",
|
||||||
"PCSt5T": "Preferences",
|
"PCSt5T": "Preferences",
|
||||||
|
"PLSbmL": "Your mnemonic phrase",
|
||||||
"Pe0ogR": "Theme",
|
"Pe0ogR": "Theme",
|
||||||
"PrsIg7": "Reactions will be shown on every page, if disabled no reactions will be shown",
|
"PrsIg7": "Reactions will be shown on every page, if disabled no reactions will be shown",
|
||||||
"QDFTjG": "{n} Relays",
|
"QDFTjG": "{n} Relays",
|
||||||
@ -142,6 +145,7 @@
|
|||||||
"R2OqnW": "Delete Account",
|
"R2OqnW": "Delete Account",
|
||||||
"RDZVQL": "Check",
|
"RDZVQL": "Check",
|
||||||
"RahCRH": "Expired",
|
"RahCRH": "Expired",
|
||||||
|
"RfhLwC": "By: {author}",
|
||||||
"RhDAoS": "Are you sure you want to delete {id}",
|
"RhDAoS": "Are you sure you want to delete {id}",
|
||||||
"RoOyAh": "Relays",
|
"RoOyAh": "Relays",
|
||||||
"Rs4kCE": "Bookmark",
|
"Rs4kCE": "Bookmark",
|
||||||
@ -159,6 +163,7 @@
|
|||||||
"W9355R": "Unmute",
|
"W9355R": "Unmute",
|
||||||
"WONP5O": "Find your twitter follows on nostr (Data provided by {provider})",
|
"WONP5O": "Find your twitter follows on nostr (Data provided by {provider})",
|
||||||
"WxthCV": "e.g. Jack",
|
"WxthCV": "e.g. Jack",
|
||||||
|
"X7xU8J": "nsec, npub, nip-05, hex, mnemonic",
|
||||||
"XgWvGA": "Reactions",
|
"XgWvGA": "Reactions",
|
||||||
"Y31HTH": "Help fund the development of Snort",
|
"Y31HTH": "Help fund the development of Snort",
|
||||||
"YDURw6": "Service URL",
|
"YDURw6": "Service URL",
|
||||||
@ -194,6 +199,7 @@
|
|||||||
"gDZkld": "Snort is a Nostr UI, nostr is a decentralised protocol for saving and distributing \"notes\".",
|
"gDZkld": "Snort is a Nostr UI, nostr is a decentralised protocol for saving and distributing \"notes\".",
|
||||||
"gDzDRs": "Emoji to send when reactiong to a note",
|
"gDzDRs": "Emoji to send when reactiong to a note",
|
||||||
"gjBiyj": "Loading...",
|
"gjBiyj": "Loading...",
|
||||||
|
"h8XMJL": "Badges",
|
||||||
"hCUivF": "Notes will stream in real time into global and posts tab",
|
"hCUivF": "Notes will stream in real time into global and posts tab",
|
||||||
"hK5ZDk": "the world",
|
"hK5ZDk": "the world",
|
||||||
"hMzcSq": "Messages",
|
"hMzcSq": "Messages",
|
||||||
@ -254,6 +260,7 @@
|
|||||||
"rfuMjE": "(Default)",
|
"rfuMjE": "(Default)",
|
||||||
"rrfdTe": "This is the same technology which is used by Bitcoin and has been proven to be extremely secure.",
|
"rrfdTe": "This is the same technology which is used by Bitcoin and has been proven to be extremely secure.",
|
||||||
"rudscU": "Failed to load follows, please try again later",
|
"rudscU": "Failed to load follows, please try again later",
|
||||||
|
"sBz4+I": "For each Fast Zap an additional {percentage}% ({amount} sats) of the zap amount will be sent to the Snort developers as a donation.",
|
||||||
"sWnYKw": "Snort is designed to have a similar experience to Twitter.",
|
"sWnYKw": "Snort is designed to have a similar experience to Twitter.",
|
||||||
"svOoEH": "Name-squatting and impersonation is not allowed. Snort and our partners reserve the right to terminate your handle (not your account - nobody can take that away) for violating this rule.",
|
"svOoEH": "Name-squatting and impersonation is not allowed. Snort and our partners reserve the right to terminate your handle (not your account - nobody can take that away) for violating this rule.",
|
||||||
"tOdNiY": "Dark",
|
"tOdNiY": "Dark",
|
||||||
|
26
yarn.lock
26
yarn.lock
@ -1681,12 +1681,12 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
eslint-scope "5.1.1"
|
eslint-scope "5.1.1"
|
||||||
|
|
||||||
"@noble/hashes@^1.2.0":
|
"@noble/hashes@^1.2.0", "@noble/hashes@~1.2.0":
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12"
|
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12"
|
||||||
integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==
|
integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==
|
||||||
|
|
||||||
"@noble/secp256k1@^1.7.0", "@noble/secp256k1@^1.7.1":
|
"@noble/secp256k1@^1.7.0", "@noble/secp256k1@^1.7.1", "@noble/secp256k1@~1.7.0":
|
||||||
version "1.7.1"
|
version "1.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c"
|
resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c"
|
||||||
integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==
|
integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==
|
||||||
@ -1789,6 +1789,28 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728"
|
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728"
|
||||||
integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==
|
integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==
|
||||||
|
|
||||||
|
"@scure/base@~1.1.0":
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938"
|
||||||
|
integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==
|
||||||
|
|
||||||
|
"@scure/bip32@^1.1.5":
|
||||||
|
version "1.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.1.5.tgz#d2ccae16dcc2e75bc1d75f5ef3c66a338d1ba300"
|
||||||
|
integrity sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==
|
||||||
|
dependencies:
|
||||||
|
"@noble/hashes" "~1.2.0"
|
||||||
|
"@noble/secp256k1" "~1.7.0"
|
||||||
|
"@scure/base" "~1.1.0"
|
||||||
|
|
||||||
|
"@scure/bip39@^1.1.1":
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5"
|
||||||
|
integrity sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==
|
||||||
|
dependencies:
|
||||||
|
"@noble/hashes" "~1.2.0"
|
||||||
|
"@scure/base" "~1.1.0"
|
||||||
|
|
||||||
"@sinclair/typebox@^0.24.1":
|
"@sinclair/typebox@^0.24.1":
|
||||||
version "0.24.51"
|
version "0.24.51"
|
||||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f"
|
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user