diff --git a/.changeset/eleven-buckets-decide.md b/.changeset/eleven-buckets-decide.md
new file mode 100644
index 000000000..0bc33a0f0
--- /dev/null
+++ b/.changeset/eleven-buckets-decide.md
@@ -0,0 +1,5 @@
+---
+"nostrudel": minor
+---
+
+Add decrypt all button to DMs
diff --git a/.changeset/smart-monkeys-boil.md b/.changeset/smart-monkeys-boil.md
new file mode 100644
index 000000000..40c01f8e0
--- /dev/null
+++ b/.changeset/smart-monkeys-boil.md
@@ -0,0 +1,5 @@
+---
+"nostrudel": minor
+---
+
+Cache decrypted events
diff --git a/src/app.tsx b/src/app.tsx
index 81e36759d..b5d685291 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -233,8 +233,11 @@ const router = createHashRouter([
{ path: "r/:relay", element: },
{ path: "notifications", element: },
{ path: "search", element: },
- { path: "dm", element: },
- { path: "dm/:key", element: },
+ {
+ path: "dm",
+ element: ,
+ children: [{ path: ":pubkey", element: }],
+ },
{ path: "profile", element: },
{
path: "tools",
diff --git a/src/providers/dycryption-provider.tsx b/src/providers/dycryption-provider.tsx
new file mode 100644
index 000000000..4fa509a7b
--- /dev/null
+++ b/src/providers/dycryption-provider.tsx
@@ -0,0 +1,142 @@
+import { PropsWithChildren, createContext, useCallback, useContext, useMemo, useRef } from "react";
+import { nanoid } from "nanoid";
+
+import Subject from "../classes/subject";
+import { useSigningContext } from "./signing-provider";
+import useSubject from "../hooks/use-subject";
+import createDefer, { Deferred } from "../classes/deferred";
+
+class DecryptionContainer {
+ id = nanoid();
+ pubkey: string;
+ data: string;
+
+ plaintext = new Subject();
+ error = new Subject();
+
+ constructor(pubkey: string, data: string) {
+ this.pubkey = pubkey;
+ this.data = data;
+ }
+}
+
+type DecryptionContextType = {
+ getOrCreateContainer: (pubkey: string, data: string) => DecryptionContainer;
+ startQueue: () => void;
+ clearQueue: () => void;
+ addToQueue: (container: DecryptionContainer) => Promise;
+ getQueue: () => DecryptionContainer[];
+};
+const DecryptionContext = createContext({
+ getOrCreateContainer: () => {
+ throw new Error("No DecryptionProvider");
+ },
+ startQueue: () => {},
+ clearQueue: () => {},
+ addToQueue: () => Promise.reject(new Error("No DecryptionProvider")),
+ getQueue: () => [],
+});
+
+export function useDecryptionContext(){
+ return useContext(DecryptionContext)
+}
+export function useDecryptionContainer(pubkey: string, data: string) {
+ const { getOrCreateContainer, addToQueue, startQueue } = useContext(DecryptionContext);
+ const container = getOrCreateContainer(pubkey, data);
+
+ const plaintext = useSubject(container.plaintext);
+ const error = useSubject(container.error);
+
+ const requestDecrypt = useCallback(() => {
+ const p = addToQueue(container);
+ startQueue();
+ return p;
+ }, [addToQueue, startQueue]);
+
+ return { container, error, plaintext, requestDecrypt };
+}
+
+export default function DecryptionProvider({ children }: PropsWithChildren) {
+ const { requestDecrypt } = useSigningContext();
+
+ const containers = useRef([]);
+ const queue = useRef([]);
+ const promises = useRef