diff --git a/packages/nextjs/app/nillion-pw-manager/page.tsx b/packages/nextjs/app/nillion-pw-manager/page.tsx
new file mode 100644
index 0000000..c8d8935
--- /dev/null
+++ b/packages/nextjs/app/nillion-pw-manager/page.tsx
@@ -0,0 +1,241 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import type * as NillionTypes from "@nillion/nillion-client-js-browser/nillion_client_js_browser.d.ts";
+import type { NextPage } from "next";
+import { useAccount } from "wagmi";
+import { CopyString } from "~~/components/nillion/CopyString";
+import Dropdown from "~~/components/nillion/Dropdown";
+import { NillionOnboarding } from "~~/components/nillion/NillionOnboarding";
+import RetrieveSecretCommand from "~~/components/nillion/RetrieveSecretCommand";
+import SecretForm from "~~/components/nillion/SecretForm";
+import { Address } from "~~/components/scaffold-eth";
+import { getUserKeyFromSnap } from "~~/utils/nillion/getUserKeyFromSnap";
+import { retrieveSecretBlob } from "~~/utils/nillion/retrieveSecretBlob";
+import { storeSecretsBlob } from "~~/utils/nillion/storeSecretsBlob";
+
+interface StoredSecrets {
+ [secretName: string]: string; // store_id
+}
+const Home: NextPage = () => {
+ const { address: connectedAddress } = useAccount();
+ const [connectedToSnap, setConnectedToSnap] = useState(false);
+ const [userKey, setUserKey] = useState(null);
+ const [userId, setUserId] = useState(null);
+ const [nillion, setNillion] = useState(null);
+ const [nillionClient, setNillionClient] = useState(null);
+ const [selectedSecretName, setSelectedSecretName] = useState("");
+ const [selectedStoreId, setSelectedStoreId] = useState(null);
+ const [latestSecretName, setLatestSecretName] = useState(null);
+ const [storedSecrets, setStoredSecrets] = useState({});
+ const [retrievedValue, setRetrievedValue] = useState(null);
+
+ async function handleConnectToSnap() {
+ const snapResponse = await getUserKeyFromSnap();
+ setUserKey(snapResponse?.user_key || null);
+ setConnectedToSnap(snapResponse?.connectedToSnap || false);
+ }
+
+ useEffect(() => {
+ if (userKey) {
+ const getNillionClientLibrary = async () => {
+ const nillionClientUtil = await import("~~/utils/nillion/nillionClient");
+ const libraries = await nillionClientUtil.getNillionClient(userKey);
+ setNillion(libraries.nillion);
+ setNillionClient(libraries.nillionClient);
+ return libraries.nillionClient;
+ };
+
+ getNillionClientLibrary().then(nillionClient => {
+ const user_id = nillionClient.user_id;
+ setUserId(user_id);
+ });
+ }
+ }, [userKey]);
+
+ async function handleSecretFormSubmit(
+ secretName: string,
+ secretValue: string,
+ permissionedUserIdForRetrieveSecret: string | null,
+ permissionedUserIdForUpdateSecret: string | null,
+ permissionedUserIdForDeleteSecret: string | null,
+ ) {
+ await storeSecretsBlob(
+ nillion,
+ nillionClient,
+ [{ name: secretName, value: secretValue }],
+ permissionedUserIdForRetrieveSecret ? [permissionedUserIdForRetrieveSecret] : [],
+ permissionedUserIdForUpdateSecret ? [permissionedUserIdForUpdateSecret] : [],
+ permissionedUserIdForDeleteSecret ? [permissionedUserIdForDeleteSecret] : [],
+ ).then((store_id: string) => {
+ setStoredSecrets(prevSecrets => ({
+ ...prevSecrets,
+ [secretName]: store_id,
+ }));
+
+ setLatestSecretName(secretName);
+ });
+ }
+
+ async function handleRetrieveSecretBlob(store_id: string, secret_name: string) {
+ await retrieveSecretBlob(nillionClient, store_id, secret_name).then(setRetrievedValue);
+ }
+
+ const handleSecretDropdownSelection = (secretName: string) => {
+ setSelectedSecretName(secretName);
+ setSelectedStoreId(storedSecrets[secretName]);
+ };
+
+ const resetNillion = () => {
+ setConnectedToSnap(false);
+ setUserKey(null);
+ setUserId(null);
+ setNillion(null);
+ setNillionClient(null);
+ };
+
+ const resetForm = () => {
+ setLatestSecretName(null);
+ setRetrievedValue(null);
+ };
+
+ useEffect(() => {
+ if (!connectedAddress) {
+ resetNillion();
+ }
+ }, [connectedAddress]);
+
+ return (
+ <>
+
+
+
+ Nillion Password Manager
+
+ {!connectedAddress &&
Connect your MetaMask Flask wallet
}
+ {connectedAddress && connectedToSnap && !userKey && (
+
+
+
+ )}
+
+
+ {connectedAddress && (
+
+
Connected Wallet Address:
+
+
+ )}
+
+ {connectedAddress && !connectedToSnap && (
+
+ )}
+
+ {connectedToSnap && (
+
+ {userKey && (
+
+
+
+ {userId && (
+
+
Connected as Nillion User ID:
+
+
+ )}
+
+ )}
+
+ )}
+
+
+
+
+ {!connectedToSnap ? (
+
+ ) : (
+
+ {/* Store secret blob */}
+
+
Store a new password
+
+
+ {latestSecretName ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
+
+
+
+
+ {/* Retrieve secret blob */}
+
+
+
Retrieve passwords from Nillion
+
+
+
+ ({ value: s, label: s }))}
+ onDropdownUpdate={selectedName => handleSecretDropdownSelection(selectedName)}
+ itemName="a password"
+ disabled={Object.keys(storedSecrets).length === 0}
+ />
+
+
+
+
+ {retrievedValue &&
✅ Retrieved value: {retrievedValue}
}
+
+
+
+
+ )}
+
+
+
+ >
+ );
+};
+
+export default Home;
diff --git a/packages/nextjs/app/page.tsx b/packages/nextjs/app/page.tsx
index cc92660..c6a25f3 100644
--- a/packages/nextjs/app/page.tsx
+++ b/packages/nextjs/app/page.tsx
@@ -38,6 +38,19 @@ const Home: NextPage = () => {
packages/hardhat/contracts
+
+
+ Nillion demo apps
+
+ Nillion Password Manager
+ {" "}
+
+ Nillion Hello World
+ {" "}
+
+ Nillion Blind Compute
+ {" "}
+
diff --git a/packages/nextjs/components/Header.tsx b/packages/nextjs/components/Header.tsx
index 7deec54..52e1c80 100644
--- a/packages/nextjs/components/Header.tsx
+++ b/packages/nextjs/components/Header.tsx
@@ -20,15 +20,15 @@ export const menuLinks: HeaderMenuLink[] = [
href: "/",
},
{
- label: "🖥️ Nillion Blind Computation Demo",
+ label: "🖥️ Blind Computation",
href: "/nillion-compute",
},
{
- label: "🎯 Nillion Hello World",
+ label: "🎯 Hello World",
href: "/nillion-hello-world",
},
{
- label: "✅ Nillion Hello World Demo",
+ label: "✅ Hello World",
href: "/nillion-hello-world-complete",
},
{
diff --git a/packages/nextjs/components/nillion/Dropdown.tsx b/packages/nextjs/components/nillion/Dropdown.tsx
new file mode 100644
index 0000000..cc3048a
--- /dev/null
+++ b/packages/nextjs/components/nillion/Dropdown.tsx
@@ -0,0 +1,36 @@
+import React, { useState } from "react";
+
+interface DropdownOption {
+ value: string;
+ label: string;
+}
+
+interface DropdownProps {
+ options: DropdownOption[];
+ onDropdownUpdate: (value: string) => void;
+ disabled?: boolean;
+ itemName?: string;
+}
+
+const Dropdown: React.FC
= ({ options, onDropdownUpdate, itemName, disabled = false }) => {
+ const [selectedOption, setSelectedOption] = useState(null);
+
+ const handleSelect = (e: React.ChangeEvent) => {
+ const selectedValue = e.target.value;
+ setSelectedOption(selectedValue);
+ onDropdownUpdate(selectedValue);
+ };
+
+ return (
+
+ );
+};
+
+export default Dropdown;
diff --git a/packages/nextjs/components/nillion/SecretForm.tsx b/packages/nextjs/components/nillion/SecretForm.tsx
index 942b3ae..9d841b5 100644
--- a/packages/nextjs/components/nillion/SecretForm.tsx
+++ b/packages/nextjs/components/nillion/SecretForm.tsx
@@ -13,6 +13,9 @@ interface SecretFormProps {
isDisabled?: boolean;
isLoading?: boolean;
secretType: "text" | "number"; // text for SecretBlob, number for SecretInteger
+ customSecretName?: boolean;
+ hidePermissions?: boolean;
+ itemName?: string;
}
const SecretForm: React.FC = ({
@@ -20,8 +23,12 @@ const SecretForm: React.FC = ({
secretName,
isDisabled = false,
isLoading = false,
- secretType, // Destructure this prop
+ customSecretName = false,
+ secretType,
+ hidePermissions = false,
+ itemName = "secret",
}) => {
+ const [secretNameFromForm, setSecretNameFromForm] = useState(secretName);
const [secret, setSecret] = useState("");
const [loading, setLoading] = useState(isLoading);
const [permissionedUserIdForRetrieveSecret, setPermissionedUserIdForRetrieveSecret] = useState("");
@@ -33,7 +40,7 @@ const SecretForm: React.FC = ({
e.preventDefault();
setLoading(true);
onSubmit(
- secretName,
+ secretNameFromForm,
secret,
permissionedUserIdForRetrieveSecret,
permissionedUserIdForUpdateSecret,
@@ -51,13 +58,31 @@ const SecretForm: React.FC = ({
"Storing secret..."
) : (