mirror of
https://github.com/logos-messaging/examples.waku.org.git
synced 2026-01-02 12:53:08 +00:00
feat: add support for Keystore and new contract (#280)
* add support for Keystore and new contract * update mock * up rln version
This commit is contained in:
parent
736f3209be
commit
5f3b0e22b0
@ -42,45 +42,69 @@
|
|||||||
<code class="value" id="latest-membership-id">Not loaded yet</code>
|
<code class="value" id="latest-membership-id">Not loaded yet</code>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="mu1">Credentials</h2>
|
<div class="row rcenter mu1">
|
||||||
|
<div>
|
||||||
|
<h2>Keystore</h2>
|
||||||
|
<span id="keystoreStatus"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button id="importKeystore" type="button">Import json</button>
|
||||||
|
<input type="file" id="importKeystoreInput" style="display: none" />
|
||||||
|
|
||||||
|
<button id="exportKeystore" type="button">Download json</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
|
<div class="row mu1">
|
||||||
|
<label for="keystorePassword"
|
||||||
|
>Password(used for reading/saving into Keystore):</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="keystorePassword"
|
||||||
|
name="keystorePassword"
|
||||||
|
type="text"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="w50">
|
<div class="w50">
|
||||||
<h4>Generate new, or import existing, credentials from wallet:</h4>
|
<h4>Generate new credentials from wallet:</h4>
|
||||||
<br />
|
<br />
|
||||||
<div id="import-from-wallet">
|
<button
|
||||||
<button id="import-from-wallet-button" type="button">
|
id="import-from-wallet-button"
|
||||||
Generate RLN Credentials
|
type="button"
|
||||||
</button>
|
style="width: 100%"
|
||||||
</div>
|
>
|
||||||
|
Generate new credentials
|
||||||
|
</button>
|
||||||
<br />
|
<br />
|
||||||
<button disabled id="register-button" type="button">
|
<button disabled id="register-button" type="button">
|
||||||
Register Credentials in Contract
|
Register credentials
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="w50">
|
<div class="w50">
|
||||||
<h4>Import existing credentials manually:</h4>
|
<h4>Read from Keystore:</h4>
|
||||||
<div>
|
<br />
|
||||||
<label for="membership-id"
|
<select
|
||||||
>Membership ID (your index in the RLN smart contract):</label
|
id="keystoreOptions"
|
||||||
>
|
style="
|
||||||
<input id="membership-id" name="membership-id" type="text" />
|
width: 100%;
|
||||||
<label for="id-secret-hash">RLN Secret Hash (hex string):</label>
|
overflow: hidden;
|
||||||
<input id="id-secret-hash" name="id-secret-hash" type="text" />
|
white-space: nowrap;
|
||||||
<label for="commitment-key">RLN Commitment Key (hex string):</label>
|
text-overflow: ellipsis;
|
||||||
<input id="commitment-key" name="commitment-key" type="text" />
|
"
|
||||||
<label for="id-trapdoor">RLN ID Trapdoor (hex string):</label>
|
></select>
|
||||||
<input id="id-trapdoor" name="id-trapdoor" type="text" />
|
<br />
|
||||||
<label for="id-nullifier">RLN ID Nullifier (hex string):</label>
|
<button id="readKeystore" type="button">Read credentials</button>
|
||||||
<input id="id-nullifier" name="id-nullifier" type="text" />
|
|
||||||
<button disabled id="import-manually-button" type="button">
|
|
||||||
Import RLN Credentials
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row rcenter mu1">
|
<div class="row rcenter mu1">
|
||||||
|
<h4>Keystore Hash</h4>
|
||||||
|
<code class="value" id="keystoreHash">none</code>
|
||||||
|
</div>
|
||||||
|
<div class="row rcenter">
|
||||||
<h4>Membership id</h4>
|
<h4>Membership id</h4>
|
||||||
<code class="value" id="id">none</code>
|
<code class="value" id="id">none</code>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,11 +8,12 @@ import {
|
|||||||
import { protobuf } from "https://taisukef.github.io/protobuf-es.js/dist/protobuf-es.js";
|
import { protobuf } from "https://taisukef.github.io/protobuf-es.js/dist/protobuf-es.js";
|
||||||
import {
|
import {
|
||||||
create,
|
create,
|
||||||
IdentityCredential,
|
Keystore,
|
||||||
RLNDecoder,
|
RLNDecoder,
|
||||||
RLNEncoder,
|
RLNEncoder,
|
||||||
RLNContract,
|
RLNContract,
|
||||||
} from "https://unpkg.com/@waku/rln@0.1.1/bundle/index.js";
|
SEPOLIA_CONTRACT,
|
||||||
|
} from "https://unpkg.com/@waku/rln@0.1.1-fa49e29/bundle/index.js";
|
||||||
import { ethers } from "https://unpkg.com/ethers@5.7.2/dist/ethers.esm.min.js";
|
import { ethers } from "https://unpkg.com/ethers@5.7.2/dist/ethers.esm.min.js";
|
||||||
|
|
||||||
const ContentTopic = "/toy-chat/2/luzhou/proto";
|
const ContentTopic = "/toy-chat/2/luzhou/proto";
|
||||||
@ -23,9 +24,6 @@ const ProtoChatMessage = new protobuf.Type("ChatMessage")
|
|||||||
.add(new protobuf.Field("nick", 2, "string"))
|
.add(new protobuf.Field("nick", 2, "string"))
|
||||||
.add(new protobuf.Field("text", 3, "bytes"));
|
.add(new protobuf.Field("text", 3, "bytes"));
|
||||||
|
|
||||||
const rlnDeployBlk = 3193048;
|
|
||||||
const rlnAddress = "0x9C09146844C1326c2dBC41c451766C7138F88155";
|
|
||||||
|
|
||||||
const SIGNATURE_MESSAGE =
|
const SIGNATURE_MESSAGE =
|
||||||
"The signature of this message will be used to generate your RLN credentials. Anyone accessing it may send messages on your behalf, please only share with the RLN dApp";
|
"The signature of this message will be used to generate your RLN credentials. Anyone accessing it may send messages on your behalf, please only share with the RLN dApp";
|
||||||
|
|
||||||
@ -56,14 +54,37 @@ async function initRLN(ui) {
|
|||||||
const rlnInstance = await create();
|
const rlnInstance = await create();
|
||||||
ui.setRlnStatus("WASM Blob download in progress... done!");
|
ui.setRlnStatus("WASM Blob download in progress... done!");
|
||||||
|
|
||||||
const rlnContract = new RLNContract(
|
const rlnContract = await RLNContract.init(rlnInstance, {
|
||||||
rlnInstance, {
|
registryAddress: SEPOLIA_CONTRACT.address,
|
||||||
address: rlnAddress,
|
|
||||||
provider: provider.getSigner(),
|
provider: provider.getSigner(),
|
||||||
});
|
});
|
||||||
|
|
||||||
result.contract = rlnContract;
|
result.contract = rlnContract;
|
||||||
|
|
||||||
|
// Keystore logic
|
||||||
|
let keystore = initKeystore(ui);
|
||||||
|
ui.createKeystoreOptions(keystore);
|
||||||
|
|
||||||
|
ui.onKeystoreImport(async (text) => {
|
||||||
|
try {
|
||||||
|
keystore = Keystore.fromString(text);
|
||||||
|
ui.setKeystoreStatus("Imported keystore from json");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to import keystore:", err);
|
||||||
|
ui.setKeystoreStatus("Failed to import, fallback to current keystore");
|
||||||
|
}
|
||||||
|
ui.createKeystoreOptions(keystore);
|
||||||
|
saveLocalKeystore(keystore);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.onKeystoreExport(async () => {
|
||||||
|
return keystore.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.onKeystoreRead(async (hash, password) => {
|
||||||
|
return keystore.readCredential(hash, password);
|
||||||
|
});
|
||||||
|
|
||||||
// Wallet logic
|
// Wallet logic
|
||||||
window.ethereum.on("accountsChanged", ui.setAccount);
|
window.ethereum.on("accountsChanged", ui.setAccount);
|
||||||
window.ethereum.on("chainChanged", (chainId) => {
|
window.ethereum.on("chainChanged", (chainId) => {
|
||||||
@ -85,7 +106,7 @@ async function initRLN(ui) {
|
|||||||
const filter = rlnContract.contract.filters.MemberRegistered();
|
const filter = rlnContract.contract.filters.MemberRegistered();
|
||||||
|
|
||||||
ui.disableRetrieveButton();
|
ui.disableRetrieveButton();
|
||||||
await rlnContract.fetchMembers(rlnInstance, { fromBlock: rlnDeployBlk });
|
await rlnContract.fetchMembers(rlnInstance);
|
||||||
ui.enableRetrieveButton();
|
ui.enableRetrieveButton();
|
||||||
|
|
||||||
rlnContract.subscribeToMembers(rlnInstance);
|
rlnContract.subscribeToMembers(rlnInstance);
|
||||||
@ -93,12 +114,12 @@ async function initRLN(ui) {
|
|||||||
const last = rlnContract.members.at(-1);
|
const last = rlnContract.members.at(-1);
|
||||||
|
|
||||||
if (last) {
|
if (last) {
|
||||||
ui.setLastMember(last.index, last.pubkey);
|
ui.setLastMember(last.index, last.idCommitment);
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure we have subscriptions to keep updating last item
|
// make sure we have subscriptions to keep updating last item
|
||||||
rlnContract.contract.on(filter, (_pubkey, _index, event) => {
|
rlnContract.contract.on(filter, (_idCommitment, _index, event) => {
|
||||||
ui.setLastMember(event.args.index, event.args.pubkey);
|
ui.setLastMember(event.args.index, event.args.idCommitment);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -106,31 +127,18 @@ async function initRLN(ui) {
|
|||||||
let membershipId;
|
let membershipId;
|
||||||
let credentials;
|
let credentials;
|
||||||
|
|
||||||
ui.onManualImport((membershipId, credentials) => {
|
|
||||||
result.encoder = new RLNEncoder(
|
|
||||||
createEncoder({
|
|
||||||
ephemeral: false,
|
|
||||||
contentTopic: ContentTopic,
|
|
||||||
}),
|
|
||||||
rlnInstance,
|
|
||||||
membershipId,
|
|
||||||
credentials
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.setMembershipInfo(membershipId, credentials);
|
|
||||||
ui.enableDialButton();
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.onWalletImport(async () => {
|
ui.onWalletImport(async () => {
|
||||||
const signer = provider.getSigner();
|
const signer = provider.getSigner();
|
||||||
|
|
||||||
signature = await signer.signMessage(SIGNATURE_MESSAGE);
|
signature = await signer.signMessage(
|
||||||
|
`${SIGNATURE_MESSAGE}. Nonce: ${Math.ceil(Math.random() * 1000)}`
|
||||||
|
);
|
||||||
credentials = await rlnInstance.generateSeededIdentityCredential(signature);
|
credentials = await rlnInstance.generateSeededIdentityCredential(signature);
|
||||||
|
|
||||||
const idCommitment = ethers.utils.hexlify(credentials.IDCommitment);
|
const idCommitment = ethers.utils.hexlify(credentials.IDCommitment);
|
||||||
|
|
||||||
rlnContract.members.forEach((m) => {
|
rlnContract.members.forEach((m) => {
|
||||||
if (m.pubkey._hex === idCommitment) {
|
if (m.idCommitment === idCommitment) {
|
||||||
membershipId = m.index.toString();
|
membershipId = m.index.toString();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -155,20 +163,42 @@ async function initRLN(ui) {
|
|||||||
|
|
||||||
ui.onRegister(async () => {
|
ui.onRegister(async () => {
|
||||||
ui.setRlnStatus("Trying to register...");
|
ui.setRlnStatus("Trying to register...");
|
||||||
const event = signature
|
const memberInfo = signature
|
||||||
? await rlnContract.registerWithSignature(rlnInstance, signature)
|
? await rlnContract.registerWithSignature(rlnInstance, signature)
|
||||||
: await rlnContract.registerWithKey(credentials);
|
: await rlnContract.registerWithKey(credentials);
|
||||||
|
|
||||||
// Update membershipId
|
membershipId = memberInfo.index.toNumber();
|
||||||
membershipId = event.args.index.toNumber();
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"Obtained index for current membership credentials",
|
"Obtained index for current membership credentials",
|
||||||
membershipId
|
membershipId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const password = ui.getKeystorePassword();
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
ui.setKeystoreStatus("Cannot add credentials, no password.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const keystoreHash = await keystore.addCredential(
|
||||||
|
{
|
||||||
|
membership: {
|
||||||
|
treeIndex: membershipId,
|
||||||
|
chainId: SEPOLIA_CONTRACT.chainId,
|
||||||
|
address: SEPOLIA_CONTRACT.address,
|
||||||
|
},
|
||||||
|
identity:
|
||||||
|
credentials ||
|
||||||
|
rlnInstance.generateSeededIdentityCredential(signature),
|
||||||
|
},
|
||||||
|
password
|
||||||
|
);
|
||||||
|
saveLocalKeystore(keystore);
|
||||||
|
ui.addKeystoreOption(keystoreHash);
|
||||||
|
ui.setKeystoreStatus(`Added credential to Keystore`);
|
||||||
|
|
||||||
ui.setRlnStatus("Successfully registered.");
|
ui.setRlnStatus("Successfully registered.");
|
||||||
ui.setMembershipInfo(membershipId, credentials);
|
ui.setMembershipInfo(membershipId, credentials, keystoreHash);
|
||||||
ui.enableDialButton();
|
ui.enableDialButton();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -284,17 +314,11 @@ function initUI() {
|
|||||||
"retrieve-rln-details"
|
"retrieve-rln-details"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Credentials Elements
|
|
||||||
const membershipIdInput = document.getElementById("membership-id");
|
|
||||||
const idSecretHashInput = document.getElementById("id-secret-hash");
|
|
||||||
const commitmentKeyInput = document.getElementById("commitment-key");
|
|
||||||
const idTrapdoorInput = document.getElementById("id-trapdoor");
|
|
||||||
const idNullifierInput = document.getElementById("id-nullifier");
|
|
||||||
const importManually = document.getElementById("import-manually-button");
|
|
||||||
const importFromWalletButton = document.getElementById(
|
const importFromWalletButton = document.getElementById(
|
||||||
"import-from-wallet-button"
|
"import-from-wallet-button"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const keystoreHashDiv = document.getElementById("keystoreHash");
|
||||||
const idDiv = document.getElementById("id");
|
const idDiv = document.getElementById("id");
|
||||||
const secretHashDiv = document.getElementById("secret-hash");
|
const secretHashDiv = document.getElementById("secret-hash");
|
||||||
const commitmentDiv = document.getElementById("commitment");
|
const commitmentDiv = document.getElementById("commitment");
|
||||||
@ -313,36 +337,25 @@ function initUI() {
|
|||||||
const sendingStatusSpan = document.getElementById("sending-status");
|
const sendingStatusSpan = document.getElementById("sending-status");
|
||||||
const messagesList = document.getElementById("messagesList");
|
const messagesList = document.getElementById("messagesList");
|
||||||
|
|
||||||
|
// Keystore
|
||||||
|
const importKeystoreBtn = document.getElementById("importKeystore");
|
||||||
|
const importKeystoreInput = document.getElementById("importKeystoreInput");
|
||||||
|
const exportKeystore = document.getElementById("exportKeystore");
|
||||||
|
const keystoreStatus = document.getElementById("keystoreStatus");
|
||||||
|
const keystorePassword = document.getElementById("keystorePassword");
|
||||||
|
const keystoreOptions = document.getElementById("keystoreOptions");
|
||||||
|
const readKeystoreButton = document.getElementById("readKeystore");
|
||||||
|
|
||||||
// set initial state
|
// set initial state
|
||||||
|
keystoreHashDiv.innerText = "not registered yet";
|
||||||
idDiv.innerText = "not registered yet";
|
idDiv.innerText = "not registered yet";
|
||||||
registerButton.disabled = true;
|
registerButton.disabled = true;
|
||||||
importManually.disabled = true;
|
|
||||||
textInput.disabled = true;
|
textInput.disabled = true;
|
||||||
sendButton.disabled = true;
|
sendButton.disabled = true;
|
||||||
dialButton.disabled = true;
|
dialButton.disabled = true;
|
||||||
retrieveRLNDetailsButton.disabled = true;
|
retrieveRLNDetailsButton.disabled = true;
|
||||||
nicknameInput.disabled = true;
|
nicknameInput.disabled = true;
|
||||||
|
|
||||||
// monitor & enable buttons if needed
|
|
||||||
membershipIdInput.onchange = enableManualImportIfNeeded;
|
|
||||||
idSecretHashInput.onchange = enableManualImportIfNeeded;
|
|
||||||
commitmentKeyInput.onchange = enableManualImportIfNeeded;
|
|
||||||
idNullifierInput.onchange = enableManualImportIfNeeded;
|
|
||||||
idTrapdoorInput.onchange = enableManualImportIfNeeded;
|
|
||||||
|
|
||||||
function enableManualImportIfNeeded() {
|
|
||||||
const isValuesPresent =
|
|
||||||
idSecretHashInput.value &&
|
|
||||||
commitmentKeyInput.value &&
|
|
||||||
idNullifierInput.value &&
|
|
||||||
idTrapdoorInput.value &&
|
|
||||||
membershipIdInput.value;
|
|
||||||
|
|
||||||
if (isValuesPresent) {
|
|
||||||
importManually.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nicknameInput.onchange = enableChatIfNeeded;
|
nicknameInput.onchange = enableChatIfNeeded;
|
||||||
nicknameInput.onblur = enableChatIfNeeded;
|
nicknameInput.onblur = enableChatIfNeeded;
|
||||||
|
|
||||||
@ -353,22 +366,32 @@ function initUI() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keystore
|
||||||
|
keystorePassword.onchange = enableRegisterIfNeeded;
|
||||||
|
keystorePassword.onblur = enableRegisterIfNeeded;
|
||||||
|
function enableRegisterIfNeeded() {
|
||||||
|
if (keystorePassword.value && commitmentDiv.innerText !== "none") {
|
||||||
|
registerButton.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// UI for RLN
|
// UI for RLN
|
||||||
setRlnStatus(text) {
|
setRlnStatus(text) {
|
||||||
statusSpan.innerText = text;
|
statusSpan.innerText = text;
|
||||||
},
|
},
|
||||||
setMembershipInfo(id, credential) {
|
setMembershipInfo(id, credential, keystoreHash) {
|
||||||
|
keystoreHashDiv.innerText = keystoreHash || "not registered yet";
|
||||||
idDiv.innerText = id || "not registered yet";
|
idDiv.innerText = id || "not registered yet";
|
||||||
secretHashDiv.innerText = utils.bytesToHex(credential.IDSecretHash);
|
secretHashDiv.innerText = utils.bytesToHex(credential.IDSecretHash);
|
||||||
commitmentDiv.innerText = utils.bytesToHex(credential.IDCommitment);
|
commitmentDiv.innerText = utils.bytesToHex(credential.IDCommitment);
|
||||||
nullifierDiv.innerText = utils.bytesToHex(credential.IDNullifier);
|
nullifierDiv.innerText = utils.bytesToHex(credential.IDNullifier);
|
||||||
trapdoorDiv.innerText = utils.bytesToHex(credential.IDTrapdoor);
|
trapdoorDiv.innerText = utils.bytesToHex(credential.IDTrapdoor);
|
||||||
},
|
},
|
||||||
setLastMember(index, pubkey) {
|
setLastMember(index, _idCommitment) {
|
||||||
try {
|
try {
|
||||||
const idCommitment = ethers.utils.zeroPad(
|
const idCommitment = ethers.utils.zeroPad(
|
||||||
ethers.utils.arrayify(pubkey),
|
ethers.utils.arrayify(_idCommitment),
|
||||||
32
|
32
|
||||||
);
|
);
|
||||||
const indexInt = index.toNumber();
|
const indexInt = index.toNumber();
|
||||||
@ -399,7 +422,15 @@ function initUI() {
|
|||||||
retrieveRLNDetailsButton.disabled = true;
|
retrieveRLNDetailsButton.disabled = true;
|
||||||
},
|
},
|
||||||
enableRegisterButtonForSepolia(chainId) {
|
enableRegisterButtonForSepolia(chainId) {
|
||||||
registerButton.disabled = isSepolia(chainId) ? false : true;
|
registerButton.disabled =
|
||||||
|
isSepolia(chainId) &&
|
||||||
|
keystorePassword.value &&
|
||||||
|
commitmentDiv.innerText !== "none"
|
||||||
|
? false
|
||||||
|
: true;
|
||||||
|
},
|
||||||
|
getKeystorePassword() {
|
||||||
|
return keystorePassword.value;
|
||||||
},
|
},
|
||||||
setAccount(accounts) {
|
setAccount(accounts) {
|
||||||
addressDiv.innerText = accounts.length ? accounts[0] : "";
|
addressDiv.innerText = accounts.length ? accounts[0] : "";
|
||||||
@ -415,24 +446,6 @@ function initUI() {
|
|||||||
await fn();
|
await fn();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onManualImport(fn) {
|
|
||||||
importManually.addEventListener("click", () => {
|
|
||||||
const idTrapdoor = utils.hexToBytes(idTrapdoorInput.value);
|
|
||||||
const idNullifier = utils.hexToBytes(idNullifierInput.value);
|
|
||||||
const idCommitment = utils.hexToBytes(commitmentKeyInput.value);
|
|
||||||
const idSecretHash = utils.hexToBytes(idSecretHashInput.value);
|
|
||||||
|
|
||||||
const membershipId = membershipIdInput.value;
|
|
||||||
const credentials = new IdentityCredential(
|
|
||||||
idTrapdoor,
|
|
||||||
idNullifier,
|
|
||||||
idSecretHash,
|
|
||||||
idCommitment
|
|
||||||
);
|
|
||||||
|
|
||||||
fn(membershipId, credentials);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onWalletImport(fn) {
|
onWalletImport(fn) {
|
||||||
importFromWalletButton.addEventListener("click", async () => {
|
importFromWalletButton.addEventListener("click", async () => {
|
||||||
await fn();
|
await fn();
|
||||||
@ -450,6 +463,68 @@ function initUI() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// Keystore
|
||||||
|
addKeystoreOption(id) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.innerText = id;
|
||||||
|
option.setAttribute("value", id);
|
||||||
|
keystoreOptions.appendChild(option);
|
||||||
|
},
|
||||||
|
createKeystoreOptions(keystore) {
|
||||||
|
const ids = Object.keys(keystore.toObject().credentials || {});
|
||||||
|
keystoreOptions.innerHTML = "";
|
||||||
|
ids.forEach((v) => this.addKeystoreOption(v));
|
||||||
|
},
|
||||||
|
onKeystoreRead(fn) {
|
||||||
|
readKeystoreButton.addEventListener("click", async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!keystoreOptions.value) {
|
||||||
|
throw Error("No value selected to read from Keystore");
|
||||||
|
}
|
||||||
|
const credentials = await fn(
|
||||||
|
keystoreOptions.value,
|
||||||
|
keystorePassword.value
|
||||||
|
);
|
||||||
|
this.setMembershipInfo(
|
||||||
|
credentials.membership.treeIndex,
|
||||||
|
credentials.identity,
|
||||||
|
keystoreOptions.value
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setKeystoreStatus(text) {
|
||||||
|
keystoreStatus.innerText = text;
|
||||||
|
},
|
||||||
|
onKeystoreImport(fn) {
|
||||||
|
importKeystoreBtn.addEventListener("click", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
importKeystoreInput.click();
|
||||||
|
});
|
||||||
|
importKeystoreInput.addEventListener("change", async (event) => {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (!file) {
|
||||||
|
console.error("No file selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const text = await file.text();
|
||||||
|
fn(text);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onKeystoreExport(fn) {
|
||||||
|
exportKeystore.addEventListener("click", async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const filename = "keystore.json";
|
||||||
|
const text = await fn();
|
||||||
|
const file = new File([text], filename, {
|
||||||
|
type: "application/json",
|
||||||
|
});
|
||||||
|
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = URL.createObjectURL(file);
|
||||||
|
link.download = filename;
|
||||||
|
link.click();
|
||||||
|
});
|
||||||
|
},
|
||||||
// UI for Waku
|
// UI for Waku
|
||||||
setWakuStatus(text) {
|
setWakuStatus(text) {
|
||||||
statusDiv.innerText = text;
|
statusDiv.innerText = text;
|
||||||
@ -501,3 +576,31 @@ function initUI() {
|
|||||||
function isSepolia(id) {
|
function isSepolia(id) {
|
||||||
return id === 11155111;
|
return id === 11155111;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initKeystore(ui) {
|
||||||
|
try {
|
||||||
|
const text = readLocalKeystore();
|
||||||
|
if (!text) {
|
||||||
|
ui.setKeystoreStatus("Initialized empty keystore");
|
||||||
|
return Keystore.create();
|
||||||
|
}
|
||||||
|
const keystore = Keystore.fromString(text);
|
||||||
|
if (!keystore) {
|
||||||
|
throw Error("Failed to create from string");
|
||||||
|
}
|
||||||
|
ui.setKeystoreStatus("Loaded from localStorage");
|
||||||
|
return keystore;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to init keystore:", err);
|
||||||
|
ui.setKeystoreStatus("Initialized empty keystore");
|
||||||
|
return Keystore.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLocalKeystore() {
|
||||||
|
return localStorage.getItem("keystore");
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveLocalKeystore(keystore) {
|
||||||
|
localStorage.setItem("keystore", keystore.toString());
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user